The previous post in this series described how the SSH-2 protocol uses a layered architecture, consisting of a transport layer, a user authentication layer, and a connection layer. This post will explain the main features of the transport layer.
The transport layer is message-based, and provides encryption, host authentication and integrity checking. Messages are sent between client and server over TCP/IP via the binary packet protocol – “packets” of data are exchanged in the format defined below, and the payload of each packet is the message:
uint32 packet_length byte padding_length byte[n1] payload; n1 = packet_length - padding_length - 1 byte[n2] random padding; n2 = padding_length byte[m] mac (Message Authentication Code - MAC); m = mac_length
The MAC is an important field, because it is the MAC that allows recipients of messages to be sure that messages have not been tampered with – the integrity checking referred to above. The MAC is calculated over the rest of the data in the packet, and uses a shared secret established between client and server, and a sequence number which both parties keep track of. MACs are described in this post.
How does a session between a client and a server begin? Each side sends an identification string once the TCP/IP connection has been established. This string is in the following format:
SSH-2.0-softwareversion SP comments CR LF
Here “SP” means a space, “CR” is a carriage return character, and “LF” is a line feed character. The software version is the vendor version, and the comments and space are optional. So in the case of CompleteFTP, when a client connects to the server they receive the following string:
The string end with the mandatory carriage return and line feed – no comments are used.
Once identification strings are exchanged, a number of options must be agreed upon – the ciphers used for encryption, the MAC algorithms used for data integrity, the key exchange methods used to set up one-time session keys for encryption, the public key algorithms that are used for authentication, and finally what compression algorithms are to be used. Both client and server send each other an SSH_MSG_KEXINIT message listing their preferences for these options:
byte SSH_MSG_KEXINIT byte cookie (random bytes) name-list kex_algorithms name-list server_host_key_algorithms name-list encryption_algorithms_client_to_server name-list encryption_algorithms_server_to_client name-list mac_algorithms_client_to_server name-list mac_algorithms_server_to_client name-list compression_algorithms_client_to_server name-list compression_algorithms_server_to_client name-list languages_client_to_server name-list languages_server_to_client boolean first_kex_packet_follows uint32 0 (reserved for future extension)
This message is the payload of a binary protocol packet whose format is described above. Name lists of algorithms are comma-separated. The client sends algorithm lists in order of preference, while the server sends a list of algorithms that it supports. The first supported algorithm in order of the client’s preference is the algorithm that is chosen. Given both messages, each side can work out what algorithms are to be used.
After SSH_MSG_KEXINIT, the selected key exchange algorithm, which may result in a number of messages being exchanged. The end result is two values: a shared secret, K, and an exchange hash, H. These are used to derive encryption and authentication keys. An SSH_MSG_NEWKEYS is sent to signify the end of these negotiations, and every subsequent message uses the new encryption keys and algorithms
Finally, with the SSH-2 connection established, the client requests a “service” with the SSH_MSG_SERVICE_REQUEST message. Usually the service will be ssh-userauth, which begins the process of user authentication, covered in the next post in this series.