SSH consists of multiple protocols in several layers, as described in brief at SSH Architecture and in full detail in RFC 4251. The layers are, from lowest to highest:
- A connection-oriented underlying transport protocol (usually TCP).
- The SSH transport layer.
- Other protocols that send messages via the SSH transport layer, including the transport layer itself (for negotiating its own parameters), the SSH user authentication layer and the SSH connection layer.
As per RFC 4253 §4, this is any 8-bit clean, binary-transparent transport. (Usually this will be TCP.) Ideally it should protect against errors, as SSH aborts the connection upon encountering a transport error.
This starts with the connection banner after which all exchanges are in a fixed packet format:
field | type | contents |
---|---|---|
len | uint32 | length l of all packet data following this word |
padlen | byte | p, length of padding below |
payload | byte[d] | length d = l - p - 1 |
padding | byte[p] | random data |
MAC | byte[m] | (unencrypted) Message Authentication Code; m = MAC length |
Though it's not terribly clear from the RFCs, the payload above always consists of fields, the first of which is always a message type identifier. The address space of these identifiers is shared amongst all the higher-level protocols that use the SSH transport protocols. The message type identifiers fall in to the following ranges:
Values | Protocol | Purpose |
---|---|---|
1-19 | SSH-TRANS | Transport layer generic (disconnect, debug etc.) |
20-29 | SSH-TRANS | Algorithm negotiation |
30-49 | SSH-TRANS | Key exchange (varies w/auth method) |
50-59 | SSH-USERAUTH | User authentication generic |
60-79 | SSH-USERAUTH | User auth specific (varies w/auth method) |
80-89 | SSH-CONNECT | Connection protocol generic |
90-127 | SSH-CONNECT | Channel-related messages |
128-191 | Reserved | |
192-255 | Local extensions |
Note that the SSH transport protocol has its own set of messages run over the basic packet format of that protocol. There is a list of the generic message numbers for all protocols.
As well as defining the base packet format, the SSH transport protocol also defines the initial parts of the connection setup, which are:
a) Session key setup, typically using Diffie Hellman key exchange. This creates an encrypted (but not authenticated) connection between the client and the server that is protected from eavesdropping by a third party.
b) Authentication of the server via verification by the client of a host public key sent to the client by the server.
c) One or more "service requests" from the client to the
server. This will typically be a request for the ssh-userauth
service, part of which requests that the ssh-connection
service
be started if the client authentication is successful.
After the first two steps all transport-layer packets are encrypted and authenticated. (During the course of the connection, the encryption parameters may be re-negotiated, e.g., to re-key the connection.)
Note that this setup is the only time the client ever checks the identity of the server; from this point all future messages in the transport protocol are known to come via the connection to that server but no further checks are done. This implies, for example, that higher-level messages could be generated by another host and just relayed by the server, such as in the case where SSH agent forwarding is used.
Once the transport level connection has been set up and the server has
accepted a request from the client for the ssh-userauth
service, the
client may send authentication requests to the server.
All SSH_MSG_USERAUTH_REQUEST
messages start with the following four
fields:
Field | Type | Description
---------|--------|---------------------------------------------
msg.type | byte | Always `SSH_MSG_USERAUTH_REQUEST`
username | string | (in UTF-8)
service | string | service to start if auth is successful (e.g., `ssh-connection`)
method | string | [method name][U§5], e.g., `publickey` or `password`
We discuss here only the publickey
authentication method. In
this case the client constructs an SSH_MSG_USERAUTH_REQUEST
message
which inlcudes the above and several further fields:
-
The fifth field is a boolean. FALSE indicates that this is a check to see if an authentication with a given public key would be acceptable; TRUE indicates that this is an actual signed authentication request.
-
The sixth and seventh fields are the public key algorithm name and the public key itself.
-
The eighth field, present only if the fifth field is TRUE, is a signature as described below.
The server response depends on the fifth field:
-
If FALSE, the response is
SSH_MSG_USERAUTH_PK_OK
if the server will accept an attempt to authenticate with the key orSSH_MSG_USERAUTH_FAILURE
if it will not accept such an attempt. -
If TRUE, the response is
SSH_MSG_USERAUTH_SUCCESS
if the server could verify the signature and no further authentications are needed, orSSH_MSG_USERAUTH_FAILURE
if either it couldn't verify the signature or if it could but further authentication is needed.
The signature is over the session identifier (from the lower-level
protocol, usually SSH-TRANS) prefixed to the SSH_MSG_USERAUTH_REQUEST
message. This is described in [U§1] as "the exchange hash H from the
first key exchange."