예제 #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());
        }
예제 #2
0
        private byte[] Hash(SequencePool sequencePool, BigInteger sharedSecret, byte[] exchangeHash, byte c, byte[] sessionId, int hashLength)
        {
            // https://tools.ietf.org/html/rfc4253#section-7.2

            byte[] hashRv     = new byte[hashLength];
            int    hashOffset = 0;

            // TODO: handle 'If the key length needed is longer than the output of the HASH'
            // HASH(K || H || c || session_id)
            using Sequence sequence = sequencePool.RentSequence();
            var writer = new SequenceWriter(sequence);

            writer.WriteMPInt(sharedSecret);
            writer.Write(exchangeHash);
            writer.WriteByte(c);
            writer.Write(sessionId);

            using IncrementalHash hash = IncrementalHash.CreateHash(_hashAlgorithmName);
            foreach (var segment in sequence.AsReadOnlySequence())
            {
                hash.AppendData(segment.Span);
            }
            byte[] K1 = hash.GetHashAndReset();
            Append(hashRv, K1, ref hashOffset);

            while (hashOffset != hashRv.Length)
            {
                // TODO: handle 'If the key length needed is longer than the output of the HASH'
                // K3 = HASH(K || H || K1 || K2)
                throw new NotSupportedException();
            }

            return(hashRv);
예제 #3
0
        private static Packet CreateServiceRequestMessage(SequencePool sequencePool)
        {
            using var packet = sequencePool.RentPacket();
            var writer = packet.GetWriter();

            writer.WriteByte(MessageNumber.SSH_MSG_SERVICE_REQUEST);
            writer.WriteString("ssh-userauth");
            return(packet.Move());
        }
예제 #4
0
 public SocketSshConnection(ILogger logger, SequencePool sequencePool, Socket socket) :
     base(sequencePool)
 {
     _logger        = logger;
     _socket        = socket;
     _receiveBuffer = sequencePool.RentSequence();
     _sendBuffer    = sequencePool.RentSequence();
     _decoder       = new PacketDecoder(SequencePool);
     _encoder       = new PacketEncoder();
 }
예제 #5
0
        private static Packet CreatePasswordRequestMessage(SequencePool sequencePool, string userName, string password)
        {
            using var packet = sequencePool.RentPacket();
            var writer = packet.GetWriter();

            writer.WriteByte(MessageNumber.SSH_MSG_USERAUTH_REQUEST);
            writer.WriteString(userName);
            writer.WriteString("ssh-connection");
            writer.WriteString("password");
            writer.WriteBoolean(false);
            writer.WriteString(password);
            return(packet.Move());
        }
예제 #6
0
        private void ReturnSegment(Segment segment)
        {
            // Return Segment buffer
            byte[]? buffer = segment.AllocatedBuffer;
            if (buffer != null)
            {
                SequencePool.ReturnByteBuffer(buffer);
            }

            // Return Segment
            segment.Reset();
            SequencePool.ReturnSegment(segment);
        }
예제 #7
0
        private void AddSegment(int sizeHint)
        {
            Segment?previousEnd = _endSegment;

            _endSegment = SequencePool.RentSegment();
            _endSegment.SetBuffer(SequencePool.RentByteBuffer(sizeHint));
            if (_startSegment == null)
            {
                _startSegment = _endSegment;
            }
            else
            {
                previousEnd !.SetNext(_endSegment);
            }
        }
예제 #8
0
        private static Packet CreatePublicKeyRequestMessage(SequencePool sequencePool, string userName, byte[] sessionId, PrivateKey privateKey)
        {
            /*
             *  byte      SSH_MSG_USERAUTH_REQUEST
             *  string    user name
             *  string    service name
             *  string    "publickey"
             *  boolean   TRUE
             *  string    public key algorithm name
             *  string    public key to be used for authentication
             *  string    signature
             */
            using var packet = sequencePool.RentPacket();
            var writer = packet.GetWriter();

            writer.WriteByte(MessageNumber.SSH_MSG_USERAUTH_REQUEST);
            writer.WriteString(userName);
            writer.WriteString("ssh-connection");
            writer.WriteString("publickey");
            writer.WriteBoolean(true);
            writer.WriteString(privateKey.Format);
            privateKey.AppendPublicKey(ref writer);
            {
                /*
                 *  string    session identifier
                 *  byte      SSH_MSG_USERAUTH_REQUEST
                 *  string    user name
                 *  string    service name
                 *  string    "publickey"
                 *  boolean   TRUE
                 *  string    public key algorithm name
                 *  string    public key to be used for authentication
                 */
                using var signatureData = sequencePool.RentSequence();
                var signatureWriter = new SequenceWriter(signatureData);
                signatureWriter.WriteString(sessionId);
                signatureWriter.WriteByte(MessageNumber.SSH_MSG_USERAUTH_REQUEST);
                signatureWriter.WriteString(userName);
                signatureWriter.WriteString("ssh-connection");
                signatureWriter.WriteString("publickey");
                signatureWriter.WriteBoolean(true);
                signatureWriter.WriteString(privateKey.Format);
                privateKey.AppendPublicKey(ref signatureWriter);
                privateKey.AppendSignature(ref writer, signatureData.AsReadOnlySequence());
            }

            return(packet.Move());
        }
예제 #9
0
 public PacketDecoder(SequencePool sequencePool) :
     this(sequencePool, EncryptionCryptoTransform.None, HMac.None)
 {
 }
예제 #10
0
 public PacketDecoder(SequencePool sequencePool, IDisposableCryptoTransform decode, IHMac mac)
 {
     _decode       = decode;
     _mac          = mac;
     _sequencePool = sequencePool;
 }
예제 #11
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));
예제 #12
0
        public static Packet RentPacket(this SequencePool sequencePool)
        {
            Sequence sequence = sequencePool.RentSequence();

            return(new Packet(sequence));
        }
예제 #13
0
        internal static async Task <SshConnection> EstablishConnectionAsync(ILogger logger, SequencePool sequencePool, SshClientSettings settings, CancellationToken ct)
        {
            Socket?socket = null;

            try
            {
                socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
                // Connect to the remote host
                logger.Connecting(settings.Host !, settings.Port);
                await socket.ConnectAsync(settings.Host !, settings.Port, ct);

                logger.ConnectionEstablished();
                socket.NoDelay = true;
                return(new SocketSshConnection(logger, sequencePool, socket));
            }
            catch
            {
                socket?.Dispose();
                throw;
            }
        }
예제 #14
0
 protected SshConnection(SequencePool sequencePool)
 {
     SequencePool = sequencePool ?? throw new ArgumentNullException(nameof(sequencePool));
 }
예제 #15
0
        public void Dispose()
        {
            Clear();

            SequencePool.ReturnSequence(this);
        }
예제 #16
0
 internal Sequence(SequencePool pool)
 {
     SequencePool = pool;
 }