From 85a27baaedb433f6e54dee2faeff872b2fc9c258 Mon Sep 17 00:00:00 2001 From: Noratrieb <48135649+Noratrieb@users.noreply.github.com> Date: Thu, 15 Aug 2024 21:37:11 +0200 Subject: [PATCH] more client --- ssh-transport/src/client.rs | 133 +++++++++++++++++++++++++----------- ssh-transport/src/crypto.rs | 83 +++++++++++----------- ssh-transport/src/packet.rs | 5 +- ssh-transport/src/server.rs | 25 ++----- 4 files changed, 145 insertions(+), 101 deletions(-) diff --git a/ssh-transport/src/client.rs b/ssh-transport/src/client.rs index 22189b6..d350cef 100644 --- a/ssh-transport/src/client.rs +++ b/ssh-transport/src/client.rs @@ -3,7 +3,10 @@ use std::{collections::VecDeque, mem}; use tracing::{debug, info, trace}; use crate::{ - crypto::{self, AlgorithmName, AlgorithmNegotiation}, + crypto::{ + self, AlgorithmName, AlgorithmNegotiation, EncryptionAlgorithm, HostKeySigningAlgorithm, + KeyExchangeSecret, SupportedAlgorithms, + }, numbers, packet::{Packet, PacketTransport, ProtocolIdentParser}, parse::{NameList, Parser, Writer}, @@ -25,6 +28,15 @@ enum ClientState { }, KexInit { client_ident: Vec, + server_ident: Vec, + }, + DhKeyInit { + client_ident: Vec, + server_ident: Vec, + kex_secret: Option, + server_hostkey_algorithm: HostKeySigningAlgorithm, + encryption_client_to_server: EncryptionAlgorithm, + encryption_server_to_client: EncryptionAlgorithm, }, } @@ -54,10 +66,10 @@ impl ClientConnection { } = &mut self.state { ident_parser.recv_bytes(bytes); - if ident_parser.get_peer_ident().is_some() { + if let Some(server_ident) = ident_parser.get_peer_ident() { let client_ident = mem::take(client_ident); // This moves to the next state. - self.send_kexinit(client_ident); + self.send_kexinit(client_ident, server_ident); return Ok(()); } return Ok(()); @@ -92,7 +104,10 @@ impl ClientConnection { match &mut self.state { ClientState::ProtoExchange { .. } => unreachable!("handled above"), - ClientState::KexInit { client_ident } => { + ClientState::KexInit { + client_ident, + server_ident, + } => { let mut kexinit = packet.payload_parser(); let packet_type = kexinit.u8()?; if packet_type != numbers::SSH_MSG_KEXINIT { @@ -101,60 +116,95 @@ impl ClientConnection { numbers::packet_type_to_string(packet_type) )); } -/* + + let sup_algs = SupportedAlgorithms::secure(); + let cookie = kexinit.array::<16>()?; + let kex_algorithm = kexinit.name_list()?; - let kex_algorithms = AlgorithmNegotiation { - supported: vec![ - crypto::KEX_CURVE_25519_SHA256, - crypto::KEX_ECDH_SHA2_NISTP256, - ], - }; - let kex_algorithm = kex_algorithms.find(kex_algorithm.0)?; + let kex_algorithm = sup_algs.key_exchange.find(kex_algorithm.0)?; debug!(name = %kex_algorithm.name(), "Using KEX algorithm"); let server_hostkey_algorithm = kexinit.name_list()?; - let server_hostkey_algorithms = AlgorithmNegotiation { - supported: vec![ - crypto::hostkey_ed25519(ED25519_PRIVKEY_BYTES.to_vec()), - crypto::hostkey_ecdsa_sha2_p256(ECDSA_P256_PRIVKEY_BYTES.to_vec()), - ], - }; let server_hostkey_algorithm = - server_hostkey_algorithms.find(server_hostkey_algorithm.0)?; + sup_algs.hostkey.find(server_hostkey_algorithm.0)?; debug!(name = %server_hostkey_algorithm.name(), "Using host key algorithm"); let encryption_algorithms_client_to_server = kexinit.name_list()?; - let encryption_algorithms_client_to_server = select_alg( - encryption_algorithms_client_to_server, - [ - crypto::encrypt::CHACHA20POLY1305, - crypto::encrypt::AES256_GCM, - ], - ); + let encryption_client_to_server = sup_algs + .encryption_to_peer + .find(encryption_algorithms_client_to_server.0)?; + debug!(name = %encryption_client_to_server.name(), "Using encryption algorithm C->S"); + let encryption_algorithms_server_to_client = kexinit.name_list()?; - let encryption_algorithms_server_to_client = select_alg( - encryption_algorithms_server_to_client, - [ - crypto::encrypt::CHACHA20POLY1305, - crypto::encrypt::AES256_GCM, - ], - ); + let encryption_server_to_client = sup_algs + .encryption_from_peer + .find(encryption_algorithms_server_to_client.0)?; + debug!(name = %encryption_server_to_client.name(), "Using encryption algorithm S->C"); + let mac_algorithms_client_to_server = kexinit.name_list()?; - select_alg(mac_algorithms_client_to_server, ["hmac-sha2-256"])?; + let _mac_client_to_server = sup_algs + .mac_to_peer + .find(mac_algorithms_client_to_server.0)?; let mac_algorithms_server_to_client = kexinit.name_list()?; - select_alg(mac_algorithms_server_to_client, ["hmac-sha2-256"])?; + let _mac_server_to_client = sup_algs + .mac_from_peer + .find(mac_algorithms_server_to_client.0)?; let compression_algorithms_client_to_server = kexinit.name_list()?; - select_alg(compression_algorithms_client_to_server, ["none"])?; + let _compression_client_to_server = sup_algs + .compression_to_peer + .find(compression_algorithms_client_to_server.0)?; let compression_algorithms_server_to_client = kexinit.name_list()?; - select_alg(compression_algorithms_server_to_client, ["none"])?; + let _compression_server_to_client = sup_algs + .compression_from_peer + .find(compression_algorithms_server_to_client.0)?; + let _languages_client_to_server = kexinit.name_list()?; let _languages_server_to_client = kexinit.name_list()?; let first_kex_packet_follows = kexinit.bool()?; if first_kex_packet_follows { return Err(peer_error!("does not support guessed kex init packages")); - }*/ + } + + let kex_secret = (kex_algorithm.generate_secret)(&mut *self.rng); + + self.packet_transport + .queue_packet(Packet::new_msg_kex_ecdh_init(&kex_secret.pubkey)); + + self.state = ClientState::DhKeyInit { + client_ident: mem::take(client_ident), + server_ident: mem::take(server_ident), + kex_secret: Some(kex_secret), + server_hostkey_algorithm, + encryption_client_to_server, + encryption_server_to_client, + }; + } + ClientState::DhKeyInit { + client_ident, + server_ident, + kex_secret, + server_hostkey_algorithm, + encryption_client_to_server, + encryption_server_to_client, + } => { + let mut dh = packet.payload_parser(); + + let packet_type = dh.u8()?; + if packet_type != numbers::SSH_MSG_KEX_ECDH_REPLY { + return Err(peer_error!( + "expected SSH_MSG_KEX_ECDH_REPLY, found {}", + numbers::packet_type_to_string(packet_type) + )); + } + + let sever_host_key = dh.string()?; + let server_ephermal_key = dh.string()?; + let signature = dh.string()?; + + let shared_secret = + (mem::take(kex_secret).unwrap().exchange)(server_ephermal_key)?; } } } @@ -165,7 +215,7 @@ impl ClientConnection { self.packet_transport.next_msg_to_send() } - fn send_kexinit(&mut self, client_ident: Vec) { + fn send_kexinit(&mut self, client_ident: Vec, server_ident: Vec) { let mut cookie = [0; 16]; self.rng.fill_bytes(&mut cookie); @@ -192,6 +242,9 @@ impl ClientConnection { self.packet_transport .queue_packet(Packet { payload: kexinit }); - self.state = ClientState::KexInit { client_ident }; + self.state = ClientState::KexInit { + client_ident, + server_ident, + }; } } diff --git a/ssh-transport/src/crypto.rs b/ssh-transport/src/crypto.rs index cba30a3..2c5790a 100644 --- a/ssh-transport/src/crypto.rs +++ b/ssh-transport/src/crypto.rs @@ -23,66 +23,69 @@ impl AlgorithmName for &'static str { #[derive(Clone, Copy)] pub struct KexAlgorithm { name: &'static str, - pub exchange: fn( - client_public_key: &[u8], - random: &mut (dyn SshRng + Send + Sync), - ) -> Result, + /// Generate an ephemeral key for the exchange. + pub generate_secret: fn(random: &mut (dyn SshRng + Send + Sync)) -> KeyExchangeSecret, } impl AlgorithmName for KexAlgorithm { fn name(&self) -> &'static str { self.name } } -pub struct KexAlgorithmOutput { - /// K - pub shared_secret: Vec, - /// Q_S - pub server_public_key: Vec, + +pub struct KeyExchangeSecret { + /// Q_x + pub pubkey: Vec, + /// Does the exchange, returning the shared secret K. + pub exchange: Box Result> + Send + Sync>, } /// pub const KEX_CURVE_25519_SHA256: KexAlgorithm = KexAlgorithm { name: "curve25519-sha256", - exchange: |client_public_key, rng| { + generate_secret: |rng| { let secret = x25519_dalek::EphemeralSecret::random_from_rng(crate::SshRngRandAdapter(rng)); - let server_public_key = x25519_dalek::PublicKey::from(&secret); // Q_S + let my_public_key = x25519_dalek::PublicKey::from(&secret); - let Ok(arr) = <[u8; 32]>::try_from(client_public_key) else { - return Err(crate::peer_error!( - "invalid x25519 public key length, should be 32, was: {}", - client_public_key.len() - )); - }; - let client_public_key = x25519_dalek::PublicKey::from(arr); - let shared_secret = secret.diffie_hellman(&client_public_key); // K + KeyExchangeSecret { + pubkey: my_public_key.as_bytes().to_vec(), + exchange: Box::new(move |peer_public_key| { + let Ok(peer_public_key) = <[u8; 32]>::try_from(peer_public_key) else { + return Err(crate::peer_error!( + "invalid x25519 public key length, should be 32, was: {}", + peer_public_key.len() + )); + }; + let peer_public_key = x25519_dalek::PublicKey::from(peer_public_key); + let shared_secret = secret.diffie_hellman(&peer_public_key); // K - Ok(KexAlgorithmOutput { - server_public_key: server_public_key.as_bytes().to_vec(), - shared_secret: shared_secret.as_bytes().to_vec(), - }) + Ok(shared_secret.as_bytes().to_vec()) + }), + } }, }; /// pub const KEX_ECDH_SHA2_NISTP256: KexAlgorithm = KexAlgorithm { name: "ecdh-sha2-nistp256", - exchange: |client_public_key, rng| { + generate_secret: |rng| { let secret = p256::ecdh::EphemeralSecret::random(&mut crate::SshRngRandAdapter(rng)); - let server_public_key = p256::EncodedPoint::from(secret.public_key()); // Q_S + let my_public_key = p256::EncodedPoint::from(secret.public_key()); - let client_public_key = - p256::PublicKey::from_sec1_bytes(client_public_key).map_err(|_| { - crate::peer_error!( - "invalid p256 public key length: {}", - client_public_key.len() - ) - })?; // Q_C + KeyExchangeSecret { + pubkey: my_public_key.as_bytes().to_vec(), + exchange: Box::new(move |peer_public_key| { + let peer_public_key = + p256::PublicKey::from_sec1_bytes(peer_public_key).map_err(|_| { + crate::peer_error!( + "invalid p256 public key length: {}", + peer_public_key.len() + ) + })?; - let shared_secret = secret.diffie_hellman(&client_public_key); // K + let shared_secret = secret.diffie_hellman(&peer_public_key); // K - Ok(KexAlgorithmOutput { - server_public_key: server_public_key.as_bytes().to_vec(), - shared_secret: shared_secret.raw_secret_bytes().to_vec(), - }) + Ok(shared_secret.raw_secret_bytes().to_vec()) + }), + } }, }; @@ -191,8 +194,8 @@ pub struct AlgorithmNegotiation { } impl AlgorithmNegotiation { - pub fn find<'a>(mut self, client_supports: &str) -> Result { - for client_alg in client_supports.split(',') { + pub fn find<'a>(mut self, peer_supports: &str) -> Result { + for client_alg in peer_supports.split(',') { if let Some(alg) = self .supported .iter() @@ -203,7 +206,7 @@ impl AlgorithmNegotiation { } Err(peer_error!( - "peer does not support any matching algorithm: peer supports: {client_supports:?}" + "peer does not support any matching algorithm: peer supports: {peer_supports:?}" )) } } diff --git a/ssh-transport/src/packet.rs b/ssh-transport/src/packet.rs index b6386f3..4adbb30 100644 --- a/ssh-transport/src/packet.rs +++ b/ssh-transport/src/packet.rs @@ -3,7 +3,7 @@ mod ctors; use std::collections::VecDeque; use std::mem; -use tracing::debug; +use tracing::{debug, trace}; use crate::crypto::{EncryptionAlgorithm, Keys, Plaintext, Session}; use crate::parse::{NameList, Parser, Writer}; @@ -82,6 +82,9 @@ impl PacketTransport { } pub(crate) fn queue_packet(&mut self, packet: Packet) { + let packet_type = packet.packet_type(); + let packet_type_string = numbers::packet_type_to_string(packet_type); + trace!(%packet_type, %packet_type_string, packet_len = %packet.payload.len(), "Sending packet"); let seq_nr = self.send_next_seq_nr; self.send_next_seq_nr = self.send_next_seq_nr.wrapping_add(1); let msg = self.keys.encrypt_packet_to_msg(packet, seq_nr); diff --git a/ssh-transport/src/server.rs b/ssh-transport/src/server.rs index fdf9bf3..e1a897c 100644 --- a/ssh-transport/src/server.rs +++ b/ssh-transport/src/server.rs @@ -1,9 +1,7 @@ -use core::str; use std::{collections::VecDeque, mem::take}; use crate::crypto::{ - self, AlgorithmName, AlgorithmNegotiation, EncryptionAlgorithm, HostKeySigningAlgorithm, - SupportedAlgorithms, + self, AlgorithmName, EncryptionAlgorithm, HostKeySigningAlgorithm, SupportedAlgorithms, }; use crate::packet::{ KeyExchangeEcDhInitPacket, KeyExchangeInitPacket, Packet, PacketTransport, ProtocolIdentParser, @@ -112,17 +110,6 @@ impl ServerConnection { } => { let kex = KeyExchangeInitPacket::parse(&packet.payload)?; - let require_algorithm = - |expected: &'static str, list: NameList<'_>| -> Result<&'static str> { - if list.iter().any(|alg| alg == expected) { - Ok(expected) - } else { - Err(peer_error!( - "client does not support algorithm {expected}. supported: {list:?}", - )) - } - }; - let sup_algs = SupportedAlgorithms::secure(); let kex_algorithm = sup_algs.key_exchange.find(kex.kex_algorithms.0)?; @@ -151,7 +138,6 @@ impl ServerConnection { let mac_algorithm_server_to_client = sup_algs .mac_to_peer .find(kex.mac_algorithms_server_to_client.0)?; - debug!("x"); let compression_algorithm_client_to_server = sup_algs .compression_from_peer @@ -159,7 +145,7 @@ impl ServerConnection { let compression_algorithm_server_to_client = sup_algs .compression_to_peer .find(kex.compression_algorithms_server_to_client.0)?; - debug!("x"); + let _ = kex.languages_client_to_server; let _ = kex.languages_server_to_client; @@ -226,10 +212,9 @@ impl ServerConnection { let client_public_key = dh.qc; - let crypto::KexAlgorithmOutput { - server_public_key, - shared_secret, - } = (kex_algorithm.exchange)(client_public_key, &mut *self.rng)?; + let server_secret = (kex_algorithm.generate_secret)(&mut *self.rng); + let server_public_key = server_secret.pubkey; + let shared_secret = (server_secret.exchange)(client_public_key)?; let pub_hostkey = server_host_key_algorithm.public_key();