use crate::constants::msg::SSH_MSG_KEXINIT;
use crate::types::Error;
use crate::types::Result;
use crate::wire::SshDecode;
use crate::wire::SshEncode;
pub struct Kexinit<'b> {
buffer: &'b [u8],
pub message_code: u8,
pub cookie: &'b [u8; 16],
pub kex_algorithms: &'b [u8],
pub server_host_key_algorithms: &'b [u8],
pub encryption_algorithms_client_to_server: &'b [u8],
pub encryption_algorithms_server_to_client: &'b [u8],
pub mac_algorithms_client_to_server: &'b [u8],
pub mac_algorithms_server_to_client: &'b [u8],
pub compression_algorithms_client_to_server: &'b [u8],
pub compression_algorithms_server_to_client: &'b [u8],
pub languages_client_to_server: &'b [u8],
pub languages_server_to_client: &'b [u8],
pub first_kex_packet_follows: bool,
pub reserved: u32,
}
impl<'b> Kexinit<'b> {
#[allow(clippy::too_many_arguments)]
pub fn write(
mut buffer: &'b mut [u8],
cookie: &[u8; 16],
kex_algorithms: &[u8],
server_host_key_algorithms: &[u8],
encryption_algorithms_client_to_server: &[u8],
encryption_algorithms_server_to_client: &[u8],
mac_algorithms_client_to_server: &[u8],
mac_algorithms_server_to_client: &[u8],
compression_algorithms_client_to_server: &[u8],
compression_algorithms_server_to_client: &[u8],
languages_client_to_server: &[u8],
languages_server_to_client: &[u8],
first_kex_packet_follows: bool,
reserved: u32,
) -> Result<(Self, usize)> {
let mut offset = 0;
buffer.write_byte(SSH_MSG_KEXINIT, &mut offset)?;
let offset_cookie = offset;
buffer.write_bytes_exact(cookie, &mut offset)?;
let offset_kex_algorithms = offset;
buffer.write_byte_string(kex_algorithms, &mut offset)?;
let offset_server_host_key_algorithms = offset;
buffer.write_byte_string(server_host_key_algorithms, &mut offset)?;
let offset_encryption_algorithms_client_to_server = offset;
buffer.write_byte_string(encryption_algorithms_client_to_server, &mut offset)?;
let offset_encryption_algorithms_server_to_client = offset;
buffer.write_byte_string(encryption_algorithms_server_to_client, &mut offset)?;
let offset_mac_algorithms_client_to_server = offset;
buffer.write_byte_string(mac_algorithms_client_to_server, &mut offset)?;
let offset_mac_algorithms_server_to_client = offset;
buffer.write_byte_string(mac_algorithms_server_to_client, &mut offset)?;
let offset_compression_algorithms_client_to_server = offset;
buffer.write_byte_string(compression_algorithms_client_to_server, &mut offset)?;
let offset_compression_algorithms_server_to_client = offset;
buffer.write_byte_string(compression_algorithms_server_to_client, &mut offset)?;
let offset_languages_client_to_server = offset;
buffer.write_byte_string(languages_client_to_server, &mut offset)?;
let offset_languages_server_to_client = offset;
buffer.write_byte_string(languages_server_to_client, &mut offset)?;
let offset_first_kex_packet_follows = offset;
buffer.write_boolean(first_kex_packet_follows, &mut offset)?;
buffer.write_uint32(reserved, &mut offset)?;
let buffer = &*buffer;
#[rustfmt::skip]
let message = Self {
buffer,
message_code: SSH_MSG_KEXINIT,
cookie: buffer[offset_cookie..offset_kex_algorithms]
.as_array()
.unwrap(),
kex_algorithms: &buffer[
4 + offset_kex_algorithms
..offset_server_host_key_algorithms
],
server_host_key_algorithms: &buffer[
4 + offset_server_host_key_algorithms
..offset_encryption_algorithms_client_to_server
],
encryption_algorithms_client_to_server: &buffer[
4 + offset_encryption_algorithms_client_to_server
..offset_encryption_algorithms_server_to_client
],
encryption_algorithms_server_to_client: &buffer[
4 + offset_encryption_algorithms_server_to_client
..offset_mac_algorithms_client_to_server
],
mac_algorithms_client_to_server: &buffer[
4 + offset_mac_algorithms_client_to_server
..offset_mac_algorithms_server_to_client
],
mac_algorithms_server_to_client: &buffer[
4 + offset_mac_algorithms_server_to_client
..offset_compression_algorithms_client_to_server
],
compression_algorithms_client_to_server: &buffer[
4 + offset_compression_algorithms_client_to_server
..offset_compression_algorithms_server_to_client
],
compression_algorithms_server_to_client: &buffer[
4 + offset_compression_algorithms_server_to_client
..offset_languages_client_to_server
],
languages_client_to_server: &buffer[
4 + offset_languages_client_to_server
..offset_languages_server_to_client
],
languages_server_to_client: &buffer[
4 + offset_languages_server_to_client
..offset_first_kex_packet_follows
],
first_kex_packet_follows,
reserved,
};
Ok((message, offset))
}
pub fn from_bytes(buffer: &'b [u8]) -> Result<Self> {
let mut offset = 0;
let message_code = buffer.read_byte(&mut offset)?;
if message_code != SSH_MSG_KEXINIT {
return Err(Error::UnexpectedMessage(message_code));
}
let cookie = buffer
.read_bytes_exact(&mut offset, 16)?
.as_array()
.unwrap();
let kex_algorithms = buffer.read_byte_string(&mut offset)?;
let server_host_key_algorithms = buffer.read_byte_string(&mut offset)?;
let encryption_algorithms_client_to_server = buffer.read_byte_string(&mut offset)?;
let encryption_algorithms_server_to_client = buffer.read_byte_string(&mut offset)?;
let mac_algorithms_client_to_server = buffer.read_byte_string(&mut offset)?;
let mac_algorithms_server_to_client = buffer.read_byte_string(&mut offset)?;
let compression_algorithms_client_to_server = buffer.read_byte_string(&mut offset)?;
let compression_algorithms_server_to_client = buffer.read_byte_string(&mut offset)?;
let languages_client_to_server = buffer.read_byte_string(&mut offset)?;
let languages_server_to_client = buffer.read_byte_string(&mut offset)?;
let first_kex_packet_follows = buffer.read_boolean(&mut offset)?;
let reserved = buffer.read_uint32(&mut offset)?;
Ok(Self {
buffer,
message_code,
cookie,
kex_algorithms,
server_host_key_algorithms,
encryption_algorithms_client_to_server,
encryption_algorithms_server_to_client,
mac_algorithms_client_to_server,
mac_algorithms_server_to_client,
compression_algorithms_client_to_server,
compression_algorithms_server_to_client,
languages_client_to_server,
languages_server_to_client,
first_kex_packet_follows,
reserved,
})
}
#[must_use]
pub fn raw(&self) -> &[u8] {
self.buffer
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::too_many_arguments, clippy::bool_assert_comparison)]
use bstr::B;
use rstest::rstest;
use super::*;
#[rustfmt::skip]
#[rstest]
#[case(
"testdata/none-exec/02-client-kexinit.bin",
SSH_MSG_KEXINIT,
"00639d6c4d9c27c5bdc504f664cbc016",
B("mlkem768x25519-sha256,sntrup761x25519-sha512,sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org,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,ext-info-c,kex-strict-c-v00@openssh.com"),
B("ssh-ed25519-cert-v01@openssh.com,ecdsa-sha2-nistp256-cert-v01@openssh.com,ecdsa-sha2-nistp384-cert-v01@openssh.com,ecdsa-sha2-nistp521-cert-v01@openssh.com,sk-ssh-ed25519-cert-v01@openssh.com,sk-ecdsa-sha2-nistp256-cert-v01@openssh.com,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ssh-ed25519,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,sk-ssh-ed25519@openssh.com,sk-ecdsa-sha2-nistp256@openssh.com,rsa-sha2-512,rsa-sha2-256"),
B("none"),
B("none"),
B("umac-64-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,umac-64@openssh.com,umac-128@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1"),
B("umac-64-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,umac-64@openssh.com,umac-128@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1"),
B("none,zlib@openssh.com"),
B("none,zlib@openssh.com"),
B(""),
B(""),
false,
0
)]
fn read_file_works(
#[case] packet_file: &str,
#[case] message_code: u8,
#[case] cookie: &str,
#[case] kex_algorithms: &[u8],
#[case] server_host_key_algorithms: &[u8],
#[case] encryption_algorithms_client_to_server: &[u8],
#[case] encryption_algorithms_server_to_client: &[u8],
#[case] mac_algorithms_client_to_server: &[u8],
#[case] mac_algorithms_server_to_client: &[u8],
#[case] compression_algorithms_client_to_server: &[u8],
#[case] compression_algorithms_server_to_client: &[u8],
#[case] languages_client_to_server: &[u8],
#[case] languages_server_to_client: &[u8],
#[case] first_kex_packet_follows: bool,
#[case] reserved: u32,
) {
let bytes = std::fs::read(packet_file).unwrap();
let packet = crate::wire::Packet::new(&bytes, 0);
let payload = packet.payload().unwrap();
let kexinit = Kexinit::from_bytes(payload).unwrap();
assert_eq!(kexinit.message_code, message_code);
assert_eq!(kexinit.cookie, hex::decode(cookie).unwrap().as_array().unwrap());
assert_eq!(kexinit.kex_algorithms, kex_algorithms);
assert_eq!(kexinit.server_host_key_algorithms, server_host_key_algorithms);
assert_eq!(kexinit.encryption_algorithms_client_to_server, encryption_algorithms_client_to_server);
assert_eq!(kexinit.encryption_algorithms_server_to_client, encryption_algorithms_server_to_client);
assert_eq!(kexinit.mac_algorithms_client_to_server, mac_algorithms_client_to_server);
assert_eq!(kexinit.mac_algorithms_server_to_client, mac_algorithms_server_to_client);
assert_eq!(kexinit.compression_algorithms_client_to_server, compression_algorithms_client_to_server);
assert_eq!(kexinit.compression_algorithms_server_to_client, compression_algorithms_server_to_client);
assert_eq!(kexinit.languages_client_to_server, languages_client_to_server);
assert_eq!(kexinit.languages_server_to_client, languages_server_to_client);
assert_eq!(kexinit.first_kex_packet_follows, first_kex_packet_follows);
assert_eq!(kexinit.reserved, reserved);
}
#[test]
fn roundtrip_works() {
let cookie = hex::decode("a28549776e59ce96ae73a58ca04224dc").unwrap();
let cookie = cookie.as_array().unwrap();
let kex_algs = b"curve25519-sha256,ecdh-sha2-nistp256";
let host_key_algs = b"ssh-ed25519,rsa-sha2-256";
let enc_c2s = b"aes128-ctr,aes256-ctr";
let enc_s2c = b"aes128-ctr,aes256-ctr";
let mac_c2s = b"hmac-sha2-256,hmac-sha2-512";
let mac_s2c = b"hmac-sha2-256,hmac-sha2-512";
let comp_c2s = b"none";
let comp_s2c = b"none";
let lang_c2s = b"";
let lang_s2c = b"";
let mut buffer = vec![0u8; 1024];
Kexinit::write(
&mut buffer,
cookie,
kex_algs,
host_key_algs,
enc_c2s,
enc_s2c,
mac_c2s,
mac_s2c,
comp_c2s,
comp_s2c,
lang_c2s,
lang_s2c,
false,
0,
)
.unwrap();
let kexinit = Kexinit::from_bytes(&buffer).unwrap();
assert_eq!(kexinit.message_code, SSH_MSG_KEXINIT);
assert_eq!(kexinit.cookie, cookie);
assert_eq!(kexinit.kex_algorithms, kex_algs);
assert_eq!(kexinit.server_host_key_algorithms, host_key_algs);
assert_eq!(kexinit.encryption_algorithms_client_to_server, enc_c2s);
assert_eq!(kexinit.encryption_algorithms_server_to_client, enc_s2c);
assert_eq!(kexinit.mac_algorithms_client_to_server, mac_c2s);
assert_eq!(kexinit.mac_algorithms_server_to_client, mac_s2c);
assert_eq!(kexinit.compression_algorithms_client_to_server, comp_c2s);
assert_eq!(kexinit.compression_algorithms_server_to_client, comp_s2c);
assert_eq!(kexinit.languages_client_to_server, lang_c2s);
assert_eq!(kexinit.languages_server_to_client, lang_s2c);
assert_eq!(kexinit.first_kex_packet_follows, false);
assert_eq!(kexinit.reserved, 0);
}
}