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()); }
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);
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()); }
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(); }
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()); }
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); }
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); } }
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()); }
public PacketDecoder(SequencePool sequencePool) : this(sequencePool, EncryptionCryptoTransform.None, HMac.None) { }
public PacketDecoder(SequencePool sequencePool, IDisposableCryptoTransform decode, IHMac mac) { _decode = decode; _mac = mac; _sequencePool = sequencePool; }
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));
public static Packet RentPacket(this SequencePool sequencePool) { Sequence sequence = sequencePool.RentSequence(); return(new Packet(sequence)); }
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; } }
protected SshConnection(SequencePool sequencePool) { SequencePool = sequencePool ?? throw new ArgumentNullException(nameof(sequencePool)); }
public void Dispose() { Clear(); SequencePool.ReturnSequence(this); }
internal Sequence(SequencePool pool) { SequencePool = pool; }