Esempio n. 1
0
        private byte[] CalculateExchangeHash(SequencePool sequencePool, SshConnectionInfo connectionInfo, Packet clientKexInitMsg, Packet serverKexInitMsg, ReadOnlySequence <byte> public_host_key, ECPoint q_c, ECPoint q_s, BigInteger sharedSecret)
        {
            /*
             *  string   V_C, client's identification string (CR and LF excluded)
             *  string   V_S, server's identification string (CR and LF excluded)
             *  string   I_C, payload of the client's SSH_MSG_KEXINIT
             *  string   I_S, payload of the server's SSH_MSG_KEXINIT
             *  string   K_S, server's public host key
             *  string   Q_C, client's ephemeral public key octet string
             *  string   Q_S, server's ephemeral public key octet string
             *  mpint    K,   shared secret
             */
            using Sequence sequence = sequencePool.RentSequence();
            var writer = new SequenceWriter(sequence);

            writer.WriteString(connectionInfo.ClientIdentificationString !);
            writer.WriteString(connectionInfo.ServerIdentificationString !);
            writer.WriteString(clientKexInitMsg.Payload);
            writer.WriteString(serverKexInitMsg.Payload);
            writer.WriteString(public_host_key);
            writer.WriteString(q_c);
            writer.WriteString(q_s);
            writer.WriteMPInt(sharedSecret);

            using IncrementalHash hash = IncrementalHash.CreateHash(_hashAlgorithmName);
            foreach (var segment in sequence.AsReadOnlySequence())
            {
                hash.AppendData(segment.Span);
            }
            return(hash.GetHashAndReset());
        }
Esempio n. 2
0
        private static async Task PerformDefaultExchange(SshConnection connection, SshConnectionInfo connectionInfo, ILogger logger, SshClientSettings settings, CancellationToken ct)
        {
            // Protocol Version Exchange: https://tools.ietf.org/html/rfc4253#section-4.2.

            // The maximum length of the string is 255 characters, including the Carriage Return and Line Feed.
            const int MaxLineLength = 255 - 2;
            // The server MAY send other lines of data before sending the version string.
            const int MaxLineReads = 20;

            AssemblyInformationalVersionAttribute?versionAttribute = typeof(SshClient).Assembly.GetCustomAttribute <AssemblyInformationalVersionAttribute>();
            string version = versionAttribute?.InformationalVersion ?? "0.0";

            version = version.Replace('-', '_');
            string identificationString = $"SSH-2.0-TmdsSsh_{version}";

            connectionInfo.ClientIdentificationString = identificationString;

            // Send our identification string.
            logger.LogInformation("Local version string {identificationString}", identificationString);
            await connection.WriteLineAsync(identificationString, ct);

            // Receive peer identification string.
            for (int i = 0; i < MaxLineReads; i++)
            {
                string line = await connection.ReceiveLineAsync(MaxLineLength, ct);

                if (line.StartsWith("SSH-", StringComparison.Ordinal))
                {
                    connectionInfo.ServerIdentificationString = line;
                    if (line.StartsWith("SSH-2.0-", StringComparison.Ordinal))
                    {
                        logger.LogInformation("Remote version string {identificationString}", connectionInfo.ServerIdentificationString);
                        return;
                    }
                    ThrowHelper.ThrowProtocolUnsupportedVersion(line);
                }
            }
            ThrowHelper.ThrowProtocolNoVersionIdentificationString();
        }
Esempio n. 3
0
 public KeyExchangeInput(IReadOnlyList <Name> hostKeyAlgorithms,
                         Packet exchangeInitMsg,
                         Packet clientKexInitMsg,
                         Packet serverKexInitMsg,
                         SshConnectionInfo connectionInfo,
                         int initialIVC2SLength,
                         int initialIVS2CLength,
                         int encryptionKeyC2SLength,
                         int encryptionKeyS2CLength,
                         int integrityKeyC2SLength,
                         int integrityKeyS2CLength)
 {
     HostKeyAlgorithms      = hostKeyAlgorithms;
     ExchangeInitMsg        = exchangeInitMsg;
     ClientKexInitMsg       = clientKexInitMsg;
     ServerKexInitMsg       = serverKexInitMsg;
     ConnectionInfo         = connectionInfo;
     InitialIVC2SLength     = initialIVC2SLength;
     InitialIVS2CLength     = initialIVS2CLength;
     EncryptionKeyC2SLength = encryptionKeyC2SLength;
     EncryptionKeyS2CLength = encryptionKeyS2CLength;
     IntegrityKeyC2SLength  = integrityKeyC2SLength;
     IntegrityKeyS2CLength  = integrityKeyS2CLength;
 }
Esempio n. 4
0
        private async static Task PerformDefaultAuthentication(SshConnection connection, ILogger logger, SshClientSettings settings, SshConnectionInfo connectionInfo, CancellationToken ct)
        {
            // TODO: handle SSH_MSG_USERAUTH_BANNER.

            // Request ssh-userauth service
            {
                using var serviceRequestMsg = CreateServiceRequestMessage(connection.SequencePool);
                await connection.SendPacketAsync(serviceRequestMsg, ct);
            }
            {
                using Packet serviceAcceptMsg = await connection.ReceivePacketAsync(ct);

                ParseServiceAccept(serviceAcceptMsg);
            }

            // Try credentials.
            foreach (var credential in settings.Credentials)
            {
                if (credential is PasswordCredential passwordCredential)
                {
                    logger.AuthenticationMethod("password");

                    using var userAuthMsg = CreatePasswordRequestMessage(connection.SequencePool,
                                                                         settings.UserName !, passwordCredential.Password);
                    await connection.SendPacketAsync(userAuthMsg, ct);
                }
                else if (credential is IdentityFileCredential ifCredential)
                {
                    if (IdentityFileCredential.TryParseFile(ifCredential.Filename, out PrivateKey? pk))
                    {
                        using (pk)
                        {
                            logger.AuthenticationMethodPublicKey(ifCredential.Filename);

                            using var userAuthMsg = CreatePublicKeyRequestMessage(connection.SequencePool,
                                                                                  settings.UserName !, connectionInfo.SessionId !, pk !);
                            await connection.SendPacketAsync(userAuthMsg, ct);
                        }
                    }
                    else
                    {
                        continue;
                    }
                }
                else
                {
                    throw new NotImplementedException("Unsupported credential type: " + credential.GetType().FullName);
                }

                // TODO...
                using Packet response = await connection.ReceivePacketAsync(ct);

                if (IsAuthSuccesfull(response))
                {
                    logger.AuthenticationSucceeded();
                    return;
                }
            }

            throw new AuthenticationFailedException();
        }
Esempio n. 5
0
        private async static Task PerformDefaultExchange(SshConnection connection, Packet clientKexInitMsg, Packet serverKexInitMsg, ILogger logger, SshClientSettings settings, SshConnectionInfo connectionInfo, CancellationToken ct)
        {
            // Key Exchange: https://tools.ietf.org/html/rfc4253#section-7.
            SequencePool sequencePool = connection.SequencePool;

            var remoteInit = ParseKeyExchangeInitMessage(serverKexInitMsg);

            // The chosen algorithm MUST be the first algorithm on the client's name-list
            // that is also on the server's name-list.
            Name encC2S = ChooseAlgorithm(settings.EncryptionAlgorithmsClientToServer, remoteInit.encryption_algorithms_client_to_server);
            Name encS2C = ChooseAlgorithm(settings.EncryptionAlgorithmsServerToClient, remoteInit.encryption_algorithms_server_to_client);
            Name macC2S = ChooseAlgorithm(settings.MacAlgorithmsClientToServer, remoteInit.mac_algorithms_client_to_server);
            Name macS2C = ChooseAlgorithm(settings.MacAlgorithmsServerToClient, remoteInit.mac_algorithms_server_to_client);
            Name comC2S = ChooseAlgorithm(settings.CompressionAlgorithmsClientToServer, remoteInit.compression_algorithms_client_to_server);
            Name comS2C = ChooseAlgorithm(settings.CompressionAlgorithmsServerToClient, remoteInit.compression_algorithms_server_to_client);

            if (encC2S.IsEmpty || encS2C.IsEmpty || macC2S.IsEmpty || macS2C.IsEmpty || comC2S.IsEmpty || comS2C.IsEmpty)
            {
                ThrowHelper.ThrowKeyExchangeFailed("No common encryption/integrity/compression algorithm.");
            }

            // Make an ordered list of host key algorithms. The key exchange algorithm will pick a compatible one.
            List <Name> hostKeyAlgorithms = new List <Name>(capacity: settings.ServerHostKeyAlgorithms.Count);

            foreach (var hostKeyAlgorithm in settings.ServerHostKeyAlgorithms)
            {
                if (remoteInit.server_host_key_algorithms.Contains(hostKeyAlgorithm))
                {
                    hostKeyAlgorithms.Add(hostKeyAlgorithm);
                }
            }

            // The first algorithm MUST be the preferred (and guessed) algorithm.  If
            // both sides make the same guess, that algorithm MUST be used.
            Name matchingKex = settings.KeyExchangeAlgorithms.Count == 0 ||
                               remoteInit.kex_algorithms.Length == 0 ||
                               settings.KeyExchangeAlgorithms[0] != remoteInit.kex_algorithms[0] ? default(Name) : settings.KeyExchangeAlgorithms[0];

            KeyExchangeOutput?keyExchangeOutput = null;
            Packet            exchangeInitMsg   = default;

            try
            {
                if (remoteInit.first_kex_packet_follows)
                {
                    exchangeInitMsg = await connection.ReceivePacketAsync(ct);

                    if (matchingKex.IsEmpty ||
                        settings.ServerHostKeyAlgorithms.Count == 0 ||
                        remoteInit.server_host_key_algorithms.Length == 0 ||
                        settings.ServerHostKeyAlgorithms[0] != remoteInit.server_host_key_algorithms[0])
                    {
                        // Silently ignore if guessed wrong.
                        exchangeInitMsg.Dispose();
                        exchangeInitMsg = default;
                    }
                    else
                    {
                        // Only accept the first hostKeyAlgorithm.
                        if (hostKeyAlgorithms.Count > 1)
                        {
                            hostKeyAlgorithms.RemoveRange(1, hostKeyAlgorithms.Count - 1);
                        }
                    }
                }

                EncryptionFactory.Default.GetKeyAndIVLength(encC2S, out int encryptionKeyC2SLength, out int initialIVC2SLength);
                EncryptionFactory.Default.GetKeyAndIVLength(encS2C, out int encryptionKeyS2CLength, out int initialIVS2CLength);
                int integrityKeyC2SLength = HMacFactory.Default.GetKeyLength(macC2S);
                int integrityKeyS2CLength = HMacFactory.Default.GetKeyLength(macS2C);

                var keyExchangeInput = new KeyExchangeInput(hostKeyAlgorithms, exchangeInitMsg, clientKexInitMsg, serverKexInitMsg, connectionInfo,
                                                            initialIVC2SLength, initialIVS2CLength, encryptionKeyC2SLength, encryptionKeyS2CLength, integrityKeyC2SLength, integrityKeyS2CLength);

                foreach (var keyAlgorithm in settings.KeyExchangeAlgorithms)
                {
                    if (remoteInit.kex_algorithms.Contains(keyAlgorithm))
                    {
                        logger.KeyExchangeAlgorithm(keyAlgorithm);

                        using (var algorithm = KeyExchangeAlgorithmFactory.Default.Create(keyAlgorithm))
                        {
                            keyExchangeOutput = await algorithm.TryExchangeAsync(connection, keyExchangeInput, logger, ct);
                        }
                        if (keyExchangeOutput != null)
                        {
                            connectionInfo.SessionId ??= keyExchangeOutput.ExchangeHash;
                            break;
                        }

                        // Preferred algorithm must be used.
                        if (!matchingKex.IsEmpty)
                        {
                            ThrowHelper.ThrowKeyExchangeFailed("Preferred key exchange algorithm failed.");
                        }
                    }
                }

                // If no algorithm satisfying all these conditions can be found, the
                // connection fails, and both sides MUST disconnect.
                if (keyExchangeOutput == null)
                {
                    ThrowHelper.ThrowKeyExchangeFailed("Key exchange failed");
                }
            }
            finally
            {
                exchangeInitMsg.Dispose();
            }

            logger.AlgorithmsServerToClient(encS2C, macS2C, comS2C);
            logger.AlgorithmsClientToServer(encC2S, macC2S, comC2S);

            // Send SSH_MSG_NEWKEYS.
            {
                using Packet newKeysMsg = CreateNewKeysMessage(sequencePool);
                await connection.SendPacketAsync(newKeysMsg, ct);
            }

            // Receive SSH_MSG_NEWKEYS.
            using Packet newKeysReceivedMsg = await connection.ReceivePacketAsync(ct);

            ParseNewKeysMessage(newKeysReceivedMsg);

            var encrypt       = EncryptionFactory.Default.CreateEncryptor(encC2S, keyExchangeOutput.EncryptionKeyC2S, keyExchangeOutput.InitialIVC2S);
            var macForEncoder = HMacFactory.Default.Create(macC2S, keyExchangeOutput.IntegrityKeyC2S);
            var decrypt       = EncryptionFactory.Default.CreateDecryptor(encS2C, keyExchangeOutput.EncryptionKeyS2C, keyExchangeOutput.InitialIVS2C);
            var macForDecoder = HMacFactory.Default.Create(macS2C, keyExchangeOutput.IntegrityKeyS2C);

            connection.SetEncoderDecoder(new PacketEncoder(encrypt, macForEncoder), new PacketDecoder(sequencePool, decrypt, macForDecoder));
Esempio n. 6
0
        private async Task ReceiveLoopAsync(SshConnection connection, SshConnectionInfo connectionInfo)
        {
            CancellationToken abortToken = _abortCts.Token;

            while (true)
            {
                var packet = await connection.ReceivePacketAsync(abortToken, maxLength : Constants.MaxPacketLength);

                if (packet.IsEmpty)
                {
                    Abort(ClosedByPeer);
                    break;
                }
                else
                {
                    // for now, eat everything.
                    packet.Dispose();
                }
                int msgType = packet.MessageType;

                // Connection Protocol: https://tools.ietf.org/html/rfc4254.

                // Dispatch to channels:
                // lock (_channels)
                // {
                //     var channelExecution = _channels[channelNumber];
                //     channelExecution.QueueReceivedPacket(packet);
                // }
                // Handle global requests
                // ...

                switch (msgType)
                {
                case MessageNumber.SSH_MSG_KEXINIT:
                    // Key Re-Exchange: https://tools.ietf.org/html/rfc4253#section-9.
                    try
                    {
                        // When we send SSH_MSG_KEXINIT, we can't send other packets until key exchange completes.
                        // This is implemented using _keyReExchangeSemaphore.
                        var keyExchangeSemaphore = new SemaphoreSlim(0, 1);
                        _keyReExchangeSemaphore = keyExchangeSemaphore;
                        // this will await _keyReExchangeSemaphore and set it to null.
                        using Packet clientKexInitMsg = KeyExchange.CreateKeyExchangeInitMessage(_sequencePool, _logger, _settings);
                        try
                        {
                            await SendPacketAsync(clientKexInitMsg, abortToken);
                        }
                        catch
                        {
                            _keyReExchangeSemaphore.Dispose();
                            _keyReExchangeSemaphore = null;
                            throw;
                        }
                        await _settings.ExchangeKeysAsync(connection, clientKexInitMsg, serverKexInitMsg : packet, _logger, _settings, connectionInfo, abortToken);

                        keyExchangeSemaphore.Release();
                    }
                    finally
                    {
                        packet.Dispose();
                    }
                    break;
                }
            }
        }
Esempio n. 7
0
        private async Task HandleConnectionAsync(SshConnection connection, SshConnectionInfo connectionInfo)
        {
            try
            {
                try
                {
                    Task sendTask = SendLoopAsync(connection);
                    AddConnectionUser(sendTask);
                    AddConnectionUser(ReceiveLoopAsync(connection, connectionInfo));

                    // Wait for a task that runs as long as the connection.
                    await sendTask.ContinueWith(_ => { /* Ignore Failed/Canceled */ });
                }
                catch (Exception e) // Unexpected: the continuation doesn't throw.
                {
                    Abort(e);
                }
                finally
                {
                    Task[] connectionUsers;
                    lock (_gate)
                    {
                        connectionUsers = _connectionUsers !.ToArray();
                        // Accept no new users.
                        _connectionUsers = null;
                    }
                    // Wait for all connection users.
                    await Task.WhenAll(connectionUsers);
                }
            }
            catch (Exception e)
            {
                Abort(e); // Unlikely, Abort will be called already.
            }
            finally
            {
                connection.Dispose();
            }

            async void AddConnectionUser(Task task)
            {
                lock (_gate)
                {
                    _connectionUsers !.Add(task);
                }

                try
                {
                    await task;
                    RemoveConnectionUser(task);
                }
                catch (Exception e)
                {
                    RemoveConnectionUser(task);
                    Abort(e);
                }

                void RemoveConnectionUser(Task task)
                {
                    lock (_gate)
                    {
                        List <Task> connectionUsers = _connectionUsers !;

                        if (connectionUsers != null)
                        {
                            connectionUsers.Remove(task);
                        }
                    }
                }
            }
        }
Esempio n. 8
0
        private async Task RunConnectionAsync(CancellationToken connectCt, TaskCompletionSource <bool> connectTcs)
        {
            SshConnection?connection     = null;
            var           connectionInfo = new SshConnectionInfo();

            try
            {
                // Cancel when:
                // * DisposeAsync is called (_abortCts)
                // * CancellationToken parameter from ConnectAsync (connectCt)
                // * Timeout from ConnectionSettings (ConnectTimeout)
                using var connectCts = CancellationTokenSource.CreateLinkedTokenSource(connectCt, _abortCts.Token);
                connectCts.CancelAfter(_settings.ConnectTimeout);

                // Connect to the remote host
                connection = await _settings.EstablishConnectionAsync(_logger, _sequencePool, _settings, connectCts.Token);

                // Setup ssh connection
                if (!_settings.NoProtocolVersionExchange)
                {
                    await _settings.ExchangeProtocolVersionAsync(connection, connectionInfo, _logger, _settings, connectCts.Token);
                }
                if (!_settings.NoKeyExchange)
                {
                    using Packet localExchangeInitMsg = KeyExchange.CreateKeyExchangeInitMessage(_sequencePool, _logger, _settings);
                    await connection.SendPacketAsync(localExchangeInitMsg, connectCts.Token);

                    {
                        using Packet remoteExchangeInitMsg = await connection.ReceivePacketAsync(connectCts.Token);

                        if (remoteExchangeInitMsg.IsEmpty)
                        {
                            ThrowHelper.ThrowProtocolUnexpectedPeerClose();
                        }
                        await _settings.ExchangeKeysAsync(connection, localExchangeInitMsg, remoteExchangeInitMsg, _logger, _settings, connectionInfo, connectCts.Token);
                    }
                }
                if (!_settings.NoUserAuthentication)
                {
                    await _settings.AuthenticateUserAsync(connection, _logger, _settings, connectionInfo, connectCts.Token);
                }

                // Allow sending.
                _sendQueue = Channel.CreateUnbounded <PendingSend>(new UnboundedChannelOptions
                {
                    SingleWriter = false,
                    SingleReader = true,
                    AllowSynchronousContinuations = true
                });
                // Allow connection users.
                _connectionUsers = new List <Task>();
                // ConnectAsync completed successfully.
                connectTcs.SetResult(true);
            }
            catch (Exception e)
            {
                connection?.Dispose();

                // In case the operation was canceled, change the exception based on the
                // token that triggered the cancellation.
                if (e is OperationCanceledException)
                {
                    if (connectCt.IsCancellationRequested)
                    {
                        connectTcs.SetCanceled();
                        return;
                    }
                    else if (_abortCts.IsCancellationRequested)
                    {
                        e = NewObjectDisposedException();
                    }
                    else
                    {
                        e = new TimeoutException();
                    }
                }

                // ConnectAsync failed.
                connectTcs.SetException(e);
                return;
            }

            await HandleConnectionAsync(connection, connectionInfo);
        }