This part of the implementation will be following RFC 4253, which documents the transport layer of the SSH protocol and the steps required to use it.
The previous post outlined the logical layers in an active SSH session. These layers are activated only after negotiating their parameters (windowing sizes, channel IDs, ciphers, hashes, and so on). Initially, the stream consists of simple ASCII text delimited by newlines and spaces.
⌨620 c ⌨128 a ⌨183 r ⌨148 g ⌨75 o ⌨105 ⌨121 r ⌨49 u ⌨131 n ⌨75 ⌨82 b ⌨76 e ⌨77 l ⌨129 a ⌨204 f ⌨156 o ⌨181 n ⌨287 t ⌨103 e ⌨232 : ⌨256 2 ⌨121 2 ⌨164 ⏎  ⎙54 ⎙0 Finished dev [unoptimized + debuginfo] target(s) in 0.01s⏎  ⎙1 ⎙0 Running `target\debug\main.exe 'belafonte:22'`⏎ ⏎  ⎙73 ▲ SSH-2.0-WhyHelloThere Its_SSH_Time!⏎  ⎙32 ▼ SSH-2.0-OpenSSH_7.6p1 Ubuntu-4ubuntu0.3⏎  ␃
Live data! How exciting. Now that the server and I have been properly introduced, the first layer — the transport layer — springs into action. All subsequent transmissions are framed in packets that add size fields, randomization, and padding.
⌨574 c ⌨80 a ⌨100 r ⌨122 g ⌨101 o ⌨92 ⌨176 r ⌨97 u ⌨137 n ⌨104 ⌨235 b ⌨151 e ⌨59 l ⌨94 a ⌨170 f ⌨299 o ⌨179 n ⌨125 t ⌨105 e ⌨223 : ⌨200 2 ⌨121 2 ⌨172 ⏎  ⎙53 ⎙0 Finished dev [unoptimized + debuginfo] target(s) in 0.01s⏎  ⎙1 ⎙0 Running `target\debug\main.exe 'belafonte:22'`⏎ ⏎  ⎙118 ▲ SSH-2.0-WhyHelloThere Its_SSH_Time!⏎  ⎙56 ▼ SSH-2.0-OpenSSH_7.6p1 Ubuntu-4ubuntu0.3⏎  ⎙92 ▼ Packet of 1081 bytes⏎  ⎙0 Content: 1069 bytes⏎  Padding: 6 bytes⏎  ␃
There was a brief moment of temptation to make the transport stream implement Rust's
std::io::Read/Write
traits, but they are clearly designed for streams rather than packets.
Adhering to these interfaces would require callers to know a magic convention (e.g. ignore the standard
library documentation and only call Write
with exactly one packet of data, making sure to never
chain this writer with anything that assumes it behaves as the documentation describes).
Although the Read/Write
traits aren't used for reading/writing packets themselves, the
traits are used for the packet payloads. As mentioned earlier, I do not want to buffer entire packets at
each layer, so layers will operate on streams of data wherever possible.
The packet writer takes care of the headers and padding around the payload, deferring the actual payload
writing to something that can Serialize
, as shown below. During the handshake, only simple
data structures will be Serialize
d, but later stages of the connection will have several
nested layers (multiplexing, windowing, etc.) behind the Serialize
trait. A
Serialize
able type must also be able to compute its length ahead of time, as the packet's
length field precedes the content (and I don't want to buffer the entire payload just to discover its
length).
It feels nice to have generics again after a few years of Go. With packets being received and unwrapped, it's time to interpret the payload.
⌨569 c ⌨80 a ⌨115 r ⌨123 g ⌨132 o ⌨93 ⌨218 r ⌨73 u ⌨102 n ⌨85 ⌨101 b ⌨214 e ⌨148 l ⌨137 a ⌨163 f ⌨70 o ⌨151 n ⌨46 t ⌨105 e ⌨214 : ⌨298 2 ⌨129 2 ⌨231 ⏎  ⎙56 ⎙0 Finished dev [unoptimized + debuginfo] target(s) in 0.01s⏎  ⎙1 ⎙0 Running `target\debug\main.exe 'belafonte:22'`⏎  ⎙121 ⏎  ⎙0 ▲ SSH-2.0-WhyHelloThere Its_SSH_Time!⏎  ⎙37 ▼ SSH-2.0-OpenSSH_7.6p1 Ubuntu-4ubuntu0.3⏎  ⎙72 ▼ Packet of 1081 bytes⏎  ⎙0 Content: 1069 bytes⏎  ⎙0 Cookie: [57, 114, 58, 223, 42, 192, 117, 82, 50, 254, 38,⏎  154, 137, 220, 221, 219]⏎  ⎙0 Key Exchange: ["curve25519-sha256",⏎  "[email protected]",⏎  "ecdh-sha2-nistp256",⏎  "ecdh-sha2-nistp384",⏎  "ecdh-sha2-nistp521",⏎  "diffie-hellman-group-exchange-sha256",⏎  "diffie-hellman-group16-sha512",⏎  "diffie-hellman-group18-sha512",⏎  "diffie-hellman-group14-sha256",⏎  "diffie-hellman-group14-sha1"]⏎  Host Key: ["ssh-rsa", "rsa-sha2-512", "rsa-sha2-256",⏎  "ecdsa-sha2-nistp256", "ssh-ed25519"]⏎  Encryption: ["[email protected]",⏎  "aes128-ctr",⏎  "aes192-ctr",⏎  "aes256-ctr",⏎  "[email protected]",⏎  "[email protected]"]⏎  MAC: ["[email protected]",⏎  "[email protected]",⏎  "[email protected]",⏎  "[email protected]",⏎  "[email protected]",⏎  "[email protected]",⏎  "[email protected]",⏎  "hmac-sha2-256",⏎  "hmac-sha2-512",⏎  "hmac-sha1"]⏎  Compression: ["none", "[email protected]"]⏎  Language: [""]⏎  Padding: 6 bytes⏎  ␃
The following layers have come into use so far, transporting connection-negotiation structures wrapped in packets:
With the list of capabilities in hand, the next step is to select from the set of available algorithms and
negotiate some keys for their use. The next post will bring the Integrity
and
Secrecy
layers to life.