1#![allow(dead_code)]
12
13#[cfg(feature = "alloc")]
14extern crate alloc;
15
16#[cfg(feature = "alloc")]
17use alloc::{collections::BTreeMap, string::String, vec, vec::Vec};
18
19pub const SSH_MSG_DISCONNECT: u8 = 1;
24pub const SSH_MSG_IGNORE: u8 = 2;
25pub const SSH_MSG_UNIMPLEMENTED: u8 = 3;
26pub const SSH_MSG_DEBUG: u8 = 4;
27pub const SSH_MSG_SERVICE_REQUEST: u8 = 5;
28pub const SSH_MSG_SERVICE_ACCEPT: u8 = 6;
29
30pub const SSH_MSG_KEXINIT: u8 = 20;
31pub const SSH_MSG_NEWKEYS: u8 = 21;
32
33pub const SSH_MSG_KEX_ECDH_INIT: u8 = 30;
34pub const SSH_MSG_KEX_ECDH_REPLY: u8 = 31;
35
36pub const SSH_MSG_USERAUTH_REQUEST: u8 = 50;
37pub const SSH_MSG_USERAUTH_FAILURE: u8 = 51;
38pub const SSH_MSG_USERAUTH_SUCCESS: u8 = 52;
39pub const SSH_MSG_USERAUTH_BANNER: u8 = 53;
40pub const SSH_MSG_USERAUTH_PK_OK: u8 = 60;
41
42pub const SSH_MSG_GLOBAL_REQUEST: u8 = 80;
43pub const SSH_MSG_REQUEST_SUCCESS: u8 = 81;
44pub const SSH_MSG_REQUEST_FAILURE: u8 = 82;
45
46pub const SSH_MSG_CHANNEL_OPEN: u8 = 90;
47pub const SSH_MSG_CHANNEL_OPEN_CONFIRMATION: u8 = 91;
48pub const SSH_MSG_CHANNEL_OPEN_FAILURE: u8 = 92;
49pub const SSH_MSG_CHANNEL_WINDOW_ADJUST: u8 = 93;
50pub const SSH_MSG_CHANNEL_DATA: u8 = 94;
51pub const SSH_MSG_CHANNEL_EXTENDED_DATA: u8 = 95;
52pub const SSH_MSG_CHANNEL_EOF: u8 = 96;
53pub const SSH_MSG_CHANNEL_CLOSE: u8 = 97;
54pub const SSH_MSG_CHANNEL_REQUEST: u8 = 98;
55pub const SSH_MSG_CHANNEL_SUCCESS: u8 = 99;
56pub const SSH_MSG_CHANNEL_FAILURE: u8 = 100;
57
58pub const SSH_DISCONNECT_HOST_NOT_ALLOWED: u32 = 1;
63pub const SSH_DISCONNECT_PROTOCOL_ERROR: u32 = 2;
64pub const SSH_DISCONNECT_KEY_EXCHANGE_FAILED: u32 = 3;
65pub const SSH_DISCONNECT_RESERVED: u32 = 4;
66pub const SSH_DISCONNECT_MAC_ERROR: u32 = 5;
67pub const SSH_DISCONNECT_COMPRESSION_ERROR: u32 = 6;
68pub const SSH_DISCONNECT_SERVICE_NOT_AVAILABLE: u32 = 7;
69pub const SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED: u32 = 8;
70pub const SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE: u32 = 9;
71pub const SSH_DISCONNECT_CONNECTION_LOST: u32 = 10;
72pub const SSH_DISCONNECT_BY_APPLICATION: u32 = 11;
73pub const SSH_DISCONNECT_TOO_MANY_CONNECTIONS: u32 = 12;
74pub const SSH_DISCONNECT_AUTH_CANCELLED_BY_USER: u32 = 13;
75pub const SSH_DISCONNECT_NO_MORE_AUTH_METHODS: u32 = 14;
76pub const SSH_DISCONNECT_ILLEGAL_USER_NAME: u32 = 15;
77
78pub const SSH_OPEN_ADMINISTRATIVELY_PROHIBITED: u32 = 1;
83pub const SSH_OPEN_CONNECT_FAILED: u32 = 2;
84pub const SSH_OPEN_UNKNOWN_CHANNEL_TYPE: u32 = 3;
85pub const SSH_OPEN_RESOURCE_SHORTAGE: u32 = 4;
86
87pub const SSH_VERSION_STRING: &[u8] = b"SSH-2.0-VeridianOS_1.0\r\n";
93
94const MAX_PACKET_SIZE: usize = 262144;
96
97const MIN_PADDING: usize = 4;
99
100const BLOCK_SIZE_CLEAR: usize = 8;
102
103const DEFAULT_WINDOW_SIZE: u32 = 2 * 1024 * 1024;
105
106const MAX_CHANNEL_DATA_SIZE: u32 = 32768;
108
109pub const SSH_DEFAULT_PORT: u16 = 22;
111
112const MAX_SESSIONS: usize = 64;
114
115const MAX_AUTH_ATTEMPTS: u32 = 6;
117
118const CURVE25519_KEY_LEN: usize = 32;
120
121const ED25519_SIG_LEN: usize = 64;
123
124const ED25519_PUB_LEN: usize = 32;
126
127const MAX_VERSION_LEN: usize = 255;
129
130const MAC_LEN: usize = 32;
132
133#[derive(Debug, Clone, Copy, PartialEq, Eq)]
139pub enum SshError {
140 InvalidPacket,
142 VersionMismatch,
144 KeyExchangeFailed,
146 AuthenticationFailed,
148 TooManyAuthAttempts,
150 ChannelNotFound,
152 ChannelExists,
154 WindowExhausted,
156 BufferTooSmall,
158 ConnectionClosed,
160 InvalidState,
162 ServiceNotAvailable,
164 PacketTooLarge,
166 MacVerifyFailed,
168 ProtocolError,
170 ResourceShortage,
172 InvalidChannelType,
174 PtyAllocationFailed,
176 SessionLimitReached,
178}
179
180#[derive(Debug, Clone, Copy, PartialEq, Eq)]
182pub enum SessionState {
183 VersionExchange,
185 KeyExchange,
187 NewKeysExpected,
189 Authentication,
191 Connected,
193 Disconnected,
195}
196
197#[derive(Debug, Clone, PartialEq, Eq)]
199pub struct NegotiatedAlgorithms {
200 pub kex_algorithm: AlgorithmId,
201 pub host_key_algorithm: AlgorithmId,
202 pub encryption_c2s: AlgorithmId,
203 pub encryption_s2c: AlgorithmId,
204 pub mac_c2s: AlgorithmId,
205 pub mac_s2c: AlgorithmId,
206 pub compression_c2s: AlgorithmId,
207 pub compression_s2c: AlgorithmId,
208}
209
210impl Default for NegotiatedAlgorithms {
211 fn default() -> Self {
212 Self::new()
213 }
214}
215
216impl NegotiatedAlgorithms {
217 pub fn new() -> Self {
218 Self {
219 kex_algorithm: AlgorithmId::Curve25519Sha256,
220 host_key_algorithm: AlgorithmId::SshEd25519,
221 encryption_c2s: AlgorithmId::Chacha20Poly1305,
222 encryption_s2c: AlgorithmId::Chacha20Poly1305,
223 mac_c2s: AlgorithmId::HmacSha256,
224 mac_s2c: AlgorithmId::HmacSha256,
225 compression_c2s: AlgorithmId::None,
226 compression_s2c: AlgorithmId::None,
227 }
228 }
229}
230
231#[derive(Debug, Clone, Copy, PartialEq, Eq)]
233pub enum AlgorithmId {
234 Curve25519Sha256,
235 SshEd25519,
236 Chacha20Poly1305,
237 Aes256Ctr,
238 HmacSha256,
239 None,
240}
241
242impl AlgorithmId {
243 pub(crate) fn as_str(&self) -> &'static str {
245 match self {
246 Self::Curve25519Sha256 => "curve25519-sha256",
247 Self::SshEd25519 => "ssh-ed25519",
248 Self::Chacha20Poly1305 => "chacha20-poly1305@openssh.com",
249 Self::Aes256Ctr => "aes256-ctr",
250 Self::HmacSha256 => "hmac-sha256",
251 Self::None => "none",
252 }
253 }
254
255 pub(crate) fn parse_name(s: &str) -> Option<Self> {
257 match s {
258 "curve25519-sha256" | "curve25519-sha256@libssh.org" => Some(Self::Curve25519Sha256),
259 "ssh-ed25519" => Some(Self::SshEd25519),
260 "chacha20-poly1305@openssh.com" => Some(Self::Chacha20Poly1305),
261 "aes256-ctr" => Some(Self::Aes256Ctr),
262 "hmac-sha256" => Some(Self::HmacSha256),
263 "none" => Some(Self::None),
264 _ => None,
265 }
266 }
267
268 pub(crate) fn as_cipher_suite(&self) -> Option<crate::crypto::cipher_suite::CipherSuite> {
274 match self {
275 Self::Chacha20Poly1305 => {
276 Some(crate::crypto::cipher_suite::CipherSuite::ChaCha20Poly1305)
277 }
278 Self::Aes256Ctr => None, _ => None,
280 }
281 }
282}
283
284#[derive(Debug, Clone, PartialEq, Eq)]
286pub struct VersionInfo {
287 pub protocol_version: [u8; 3],
289 pub software_version: Vec<u8>,
291 pub comment: Vec<u8>,
293}
294
295impl VersionInfo {
296 pub(crate) fn parse(data: &[u8]) -> Option<Self> {
298 if data.len() < 8 || &data[..4] != b"SSH-" {
300 return None;
301 }
302
303 let end = data.iter().position(|&b| b == b'\n').unwrap_or(data.len());
305 let line = if end > 0 && data[end - 1] == b'\r' {
306 &data[..end - 1]
307 } else {
308 &data[..end]
309 };
310
311 if line.len() > MAX_VERSION_LEN {
312 return None;
313 }
314
315 if line.len() < 7 || &line[4..7] != b"2.0" {
317 return None;
318 }
319
320 let protocol_version = [b'2', b'.', b'0'];
321
322 if line.len() < 8 || line[7] != b'-' {
324 return None;
325 }
326
327 let sw_start = 8;
329 let sw_end = line[sw_start..]
330 .iter()
331 .position(|&b| b == b' ')
332 .map(|p| sw_start + p)
333 .unwrap_or(line.len());
334
335 let software_version = line[sw_start..sw_end].to_vec();
336
337 let comment = if sw_end < line.len() {
338 line[sw_end + 1..].to_vec()
339 } else {
340 Vec::new()
341 };
342
343 Some(Self {
344 protocol_version,
345 software_version,
346 comment,
347 })
348 }
349
350 pub(crate) fn encode(&self) -> Vec<u8> {
352 let mut buf = Vec::with_capacity(64);
353 buf.extend_from_slice(b"SSH-");
354 buf.extend_from_slice(&self.protocol_version);
355 buf.push(b'-');
356 buf.extend_from_slice(&self.software_version);
357 if !self.comment.is_empty() {
358 buf.push(b' ');
359 buf.extend_from_slice(&self.comment);
360 }
361 buf.extend_from_slice(b"\r\n");
362 buf
363 }
364}
365
366#[derive(Debug, Clone, PartialEq, Eq)]
372pub struct SshPacket {
373 pub payload: Vec<u8>,
375 pub padding: Vec<u8>,
377}
378
379impl SshPacket {
380 pub fn new(payload: Vec<u8>) -> Self {
382 let padding = Self::compute_padding(payload.len(), BLOCK_SIZE_CLEAR);
383 Self { payload, padding }
384 }
385
386 fn compute_padding(payload_len: usize, block_size: usize) -> Vec<u8> {
388 let unpadded = 1 + payload_len; let remainder = (4 + unpadded) % block_size;
392 let mut pad_len = if remainder == 0 {
393 0
394 } else {
395 block_size - remainder
396 };
397 if pad_len < MIN_PADDING {
398 pad_len += block_size;
399 }
400 vec![0u8; pad_len]
402 }
403
404 pub(crate) fn encode(&self) -> Vec<u8> {
406 let padding_length = self.padding.len() as u8;
407 let packet_length = (1 + self.payload.len() + self.padding.len()) as u32;
408 let total = 4 + packet_length as usize;
409
410 let mut buf = Vec::with_capacity(total);
411 buf.extend_from_slice(&packet_length.to_be_bytes());
412 buf.push(padding_length);
413 buf.extend_from_slice(&self.payload);
414 buf.extend_from_slice(&self.padding);
415 buf
416 }
417
418 pub(crate) fn decode(data: &[u8]) -> Result<(Self, usize), SshError> {
420 if data.len() < 5 {
421 return Err(SshError::InvalidPacket);
422 }
423
424 let packet_length = u32::from_be_bytes([data[0], data[1], data[2], data[3]]) as usize;
425
426 if !(2..=MAX_PACKET_SIZE).contains(&packet_length) {
427 return Err(SshError::PacketTooLarge);
428 }
429
430 let total = 4 + packet_length;
431 if data.len() < total {
432 return Err(SshError::InvalidPacket);
433 }
434
435 let padding_length = data[4] as usize;
436 if padding_length < MIN_PADDING || padding_length >= packet_length {
437 return Err(SshError::InvalidPacket);
438 }
439
440 let payload_length = packet_length - 1 - padding_length;
441 let payload = data[5..5 + payload_length].to_vec();
442 let padding = data[5 + payload_length..5 + payload_length + padding_length].to_vec();
443
444 Ok((Self { payload, padding }, total))
445 }
446
447 pub(crate) fn message_type(&self) -> Option<u8> {
449 self.payload.first().copied()
450 }
451}
452
453#[derive(Debug, Clone, PartialEq, Eq)]
455pub struct KexInitMessage {
456 pub cookie: [u8; 16],
458 pub kex_algorithms: Vec<u8>,
460 pub server_host_key_algorithms: Vec<u8>,
462 pub encryption_algorithms_c2s: Vec<u8>,
464 pub encryption_algorithms_s2c: Vec<u8>,
466 pub mac_algorithms_c2s: Vec<u8>,
468 pub mac_algorithms_s2c: Vec<u8>,
470 pub compression_algorithms_c2s: Vec<u8>,
472 pub compression_algorithms_s2c: Vec<u8>,
474 pub languages_c2s: Vec<u8>,
476 pub languages_s2c: Vec<u8>,
478 pub first_kex_packet_follows: bool,
480}
481
482impl KexInitMessage {
483 pub fn new_server(cookie: [u8; 16]) -> Self {
485 Self {
486 cookie,
487 kex_algorithms: Self::encode_name_list(&["curve25519-sha256"]),
488 server_host_key_algorithms: Self::encode_name_list(&["ssh-ed25519"]),
489 encryption_algorithms_c2s: Self::encode_name_list(&[
490 "chacha20-poly1305@openssh.com",
491 "aes256-ctr",
492 ]),
493 encryption_algorithms_s2c: Self::encode_name_list(&[
494 "chacha20-poly1305@openssh.com",
495 "aes256-ctr",
496 ]),
497 mac_algorithms_c2s: Self::encode_name_list(&["hmac-sha256"]),
498 mac_algorithms_s2c: Self::encode_name_list(&["hmac-sha256"]),
499 compression_algorithms_c2s: Self::encode_name_list(&["none"]),
500 compression_algorithms_s2c: Self::encode_name_list(&["none"]),
501 languages_c2s: Self::encode_name_list(&[]),
502 languages_s2c: Self::encode_name_list(&[]),
503 first_kex_packet_follows: false,
504 }
505 }
506
507 fn encode_name_list(names: &[&str]) -> Vec<u8> {
509 let mut joined = Vec::new();
510 for (i, name) in names.iter().enumerate() {
511 if i > 0 {
512 joined.push(b',');
513 }
514 joined.extend_from_slice(name.as_bytes());
515 }
516 joined
517 }
518
519 pub(crate) fn encode(&self) -> Vec<u8> {
521 let mut buf = Vec::with_capacity(512);
522 buf.push(SSH_MSG_KEXINIT);
523 buf.extend_from_slice(&self.cookie);
524
525 fn write_name_list(buf: &mut Vec<u8>, data: &[u8]) {
527 buf.extend_from_slice(&(data.len() as u32).to_be_bytes());
528 buf.extend_from_slice(data);
529 }
530
531 write_name_list(&mut buf, &self.kex_algorithms);
532 write_name_list(&mut buf, &self.server_host_key_algorithms);
533 write_name_list(&mut buf, &self.encryption_algorithms_c2s);
534 write_name_list(&mut buf, &self.encryption_algorithms_s2c);
535 write_name_list(&mut buf, &self.mac_algorithms_c2s);
536 write_name_list(&mut buf, &self.mac_algorithms_s2c);
537 write_name_list(&mut buf, &self.compression_algorithms_c2s);
538 write_name_list(&mut buf, &self.compression_algorithms_s2c);
539 write_name_list(&mut buf, &self.languages_c2s);
540 write_name_list(&mut buf, &self.languages_s2c);
541
542 buf.push(if self.first_kex_packet_follows { 1 } else { 0 });
543 buf.extend_from_slice(&0u32.to_be_bytes());
545
546 buf
547 }
548
549 pub(crate) fn decode(data: &[u8]) -> Result<Self, SshError> {
551 if data.len() < 17 {
552 return Err(SshError::InvalidPacket);
553 }
554
555 if data[0] != SSH_MSG_KEXINIT {
557 return Err(SshError::InvalidPacket);
558 }
559
560 let mut cookie = [0u8; 16];
561 cookie.copy_from_slice(&data[1..17]);
562
563 let mut pos = 17;
564
565 fn read_name_list(data: &[u8], pos: &mut usize) -> Result<Vec<u8>, SshError> {
566 if *pos + 4 > data.len() {
567 return Err(SshError::InvalidPacket);
568 }
569 let len =
570 u32::from_be_bytes([data[*pos], data[*pos + 1], data[*pos + 2], data[*pos + 3]])
571 as usize;
572 *pos += 4;
573 if *pos + len > data.len() {
574 return Err(SshError::InvalidPacket);
575 }
576 let result = data[*pos..*pos + len].to_vec();
577 *pos += len;
578 Ok(result)
579 }
580
581 let kex_algorithms = read_name_list(data, &mut pos)?;
582 let server_host_key_algorithms = read_name_list(data, &mut pos)?;
583 let encryption_algorithms_c2s = read_name_list(data, &mut pos)?;
584 let encryption_algorithms_s2c = read_name_list(data, &mut pos)?;
585 let mac_algorithms_c2s = read_name_list(data, &mut pos)?;
586 let mac_algorithms_s2c = read_name_list(data, &mut pos)?;
587 let compression_algorithms_c2s = read_name_list(data, &mut pos)?;
588 let compression_algorithms_s2c = read_name_list(data, &mut pos)?;
589 let languages_c2s = read_name_list(data, &mut pos)?;
590 let languages_s2c = read_name_list(data, &mut pos)?;
591
592 let first_kex_packet_follows = if pos < data.len() {
593 data[pos] != 0
594 } else {
595 false
596 };
597
598 Ok(Self {
599 cookie,
600 kex_algorithms,
601 server_host_key_algorithms,
602 encryption_algorithms_c2s,
603 encryption_algorithms_s2c,
604 mac_algorithms_c2s,
605 mac_algorithms_s2c,
606 compression_algorithms_c2s,
607 compression_algorithms_s2c,
608 languages_c2s,
609 languages_s2c,
610 first_kex_packet_follows,
611 })
612 }
613}
614
615#[derive(Debug, Clone, PartialEq, Eq)]
617pub struct KexState {
618 pub server_ephemeral_private: [u8; CURVE25519_KEY_LEN],
620 pub server_ephemeral_public: [u8; CURVE25519_KEY_LEN],
622 pub client_ephemeral_public: [u8; CURVE25519_KEY_LEN],
624 pub shared_secret: [u8; CURVE25519_KEY_LEN],
626 pub exchange_hash: [u8; 32],
628 pub complete: bool,
630}
631
632impl Default for KexState {
633 fn default() -> Self {
634 Self::new()
635 }
636}
637
638impl KexState {
639 pub fn new() -> Self {
640 Self {
641 server_ephemeral_private: [0u8; CURVE25519_KEY_LEN],
642 server_ephemeral_public: [0u8; CURVE25519_KEY_LEN],
643 client_ephemeral_public: [0u8; CURVE25519_KEY_LEN],
644 shared_secret: [0u8; CURVE25519_KEY_LEN],
645 exchange_hash: [0u8; 32],
646 complete: false,
647 }
648 }
649
650 pub(crate) fn set_server_keys(&mut self, private: [u8; 32], public: [u8; 32]) {
652 self.server_ephemeral_private = private;
653 self.server_ephemeral_public = public;
654 }
655
656 pub(crate) fn set_client_public(&mut self, key: [u8; 32]) {
658 self.client_ephemeral_public = key;
659 }
660}
661
662#[derive(Debug, Clone, PartialEq, Eq)]
664pub struct TransportKeys {
665 pub enc_key_c2s: [u8; 32],
667 pub enc_key_s2c: [u8; 32],
669 pub mac_key_c2s: [u8; 32],
671 pub mac_key_s2c: [u8; 32],
673 pub iv_c2s: [u8; 12],
675 pub iv_s2c: [u8; 12],
677}
678
679impl Default for TransportKeys {
680 fn default() -> Self {
681 Self::new()
682 }
683}
684
685impl TransportKeys {
686 pub fn new() -> Self {
687 Self {
688 enc_key_c2s: [0u8; 32],
689 enc_key_s2c: [0u8; 32],
690 mac_key_c2s: [0u8; 32],
691 mac_key_s2c: [0u8; 32],
692 iv_c2s: [0u8; 12],
693 iv_s2c: [0u8; 12],
694 }
695 }
696}
697
698pub(crate) fn encode_disconnect(reason_code: u32, description: &str) -> Vec<u8> {
700 let desc_bytes = description.as_bytes();
701 let mut buf = Vec::with_capacity(16 + desc_bytes.len());
702 buf.push(SSH_MSG_DISCONNECT);
703 buf.extend_from_slice(&reason_code.to_be_bytes());
704 buf.extend_from_slice(&(desc_bytes.len() as u32).to_be_bytes());
706 buf.extend_from_slice(desc_bytes);
707 buf.extend_from_slice(&0u32.to_be_bytes());
709 buf
710}
711
712pub(crate) fn encode_newkeys() -> Vec<u8> {
714 vec![SSH_MSG_NEWKEYS]
715}
716
717pub(crate) fn encode_service_accept(service_name: &str) -> Vec<u8> {
719 let name_bytes = service_name.as_bytes();
720 let mut buf = Vec::with_capacity(5 + name_bytes.len());
721 buf.push(SSH_MSG_SERVICE_ACCEPT);
722 buf.extend_from_slice(&(name_bytes.len() as u32).to_be_bytes());
723 buf.extend_from_slice(name_bytes);
724 buf
725}
726
727pub(crate) fn parse_service_request(payload: &[u8]) -> Result<Vec<u8>, SshError> {
729 if payload.is_empty() || payload[0] != SSH_MSG_SERVICE_REQUEST {
730 return Err(SshError::InvalidPacket);
731 }
732 if payload.len() < 5 {
733 return Err(SshError::InvalidPacket);
734 }
735 let len = u32::from_be_bytes([payload[1], payload[2], payload[3], payload[4]]) as usize;
736 if payload.len() < 5 + len {
737 return Err(SshError::InvalidPacket);
738 }
739 Ok(payload[5..5 + len].to_vec())
740}
741
742#[derive(Debug, Clone, Copy, PartialEq, Eq)]
748pub enum AuthMethod {
749 None,
751 Password,
753 PublicKey,
755 KeyboardInteractive,
757}
758
759impl AuthMethod {
760 pub(crate) fn as_str(&self) -> &'static str {
761 match self {
762 Self::None => "none",
763 Self::Password => "password",
764 Self::PublicKey => "publickey",
765 Self::KeyboardInteractive => "keyboard-interactive",
766 }
767 }
768
769 pub(crate) fn from_bytes(data: &[u8]) -> Option<Self> {
770 match data {
771 b"none" => Some(Self::None),
772 b"password" => Some(Self::Password),
773 b"publickey" => Some(Self::PublicKey),
774 b"keyboard-interactive" => Some(Self::KeyboardInteractive),
775 _ => None,
776 }
777 }
778}
779
780#[derive(Debug, Clone, PartialEq, Eq)]
782pub struct AuthState {
783 pub username: Vec<u8>,
785 pub attempts: u32,
787 pub max_attempts: u32,
789 pub authenticated: bool,
791 pub service_name: Vec<u8>,
793 pub allowed_methods: Vec<AuthMethod>,
795 pub partial_success: bool,
797}
798
799impl Default for AuthState {
800 fn default() -> Self {
801 Self::new()
802 }
803}
804
805impl AuthState {
806 pub fn new() -> Self {
807 Self {
808 username: Vec::new(),
809 attempts: 0,
810 max_attempts: MAX_AUTH_ATTEMPTS,
811 authenticated: false,
812 service_name: Vec::new(),
813 allowed_methods: vec![AuthMethod::Password, AuthMethod::PublicKey],
814 partial_success: false,
815 }
816 }
817
818 pub(crate) fn record_failure(&mut self) -> bool {
820 self.attempts += 1;
821 self.attempts < self.max_attempts
822 }
823
824 pub(crate) fn mark_success(&mut self) {
826 self.authenticated = true;
827 }
828
829 pub(crate) fn exhausted(&self) -> bool {
831 self.attempts >= self.max_attempts
832 }
833}
834
835#[derive(Debug, Clone, PartialEq, Eq)]
837pub struct UserauthRequest {
838 pub username: Vec<u8>,
840 pub service_name: Vec<u8>,
842 pub method: AuthMethod,
844 pub method_data: AuthMethodData,
846}
847
848#[derive(Debug, Clone, PartialEq, Eq)]
850pub enum AuthMethodData {
851 None,
853 Password {
855 password: Vec<u8>,
857 },
858 PublicKey {
860 has_signature: bool,
862 algorithm: Vec<u8>,
864 public_key: Vec<u8>,
866 signature: Vec<u8>,
868 },
869}
870
871pub(crate) fn parse_userauth_request(payload: &[u8]) -> Result<UserauthRequest, SshError> {
873 if payload.is_empty() || payload[0] != SSH_MSG_USERAUTH_REQUEST {
874 return Err(SshError::InvalidPacket);
875 }
876
877 let mut pos = 1;
878
879 let username = read_ssh_string(payload, &mut pos)?;
881 let service_name = read_ssh_string(payload, &mut pos)?;
883 let method_name = read_ssh_string(payload, &mut pos)?;
885
886 let method = AuthMethod::from_bytes(&method_name).ok_or(SshError::InvalidPacket)?;
887
888 let method_data = match method {
889 AuthMethod::None => AuthMethodData::None,
890 AuthMethod::Password => {
891 if pos >= payload.len() {
893 return Err(SshError::InvalidPacket);
894 }
895 let _change_password = payload[pos];
896 pos += 1;
897 let password = read_ssh_string(payload, &mut pos)?;
898 AuthMethodData::Password { password }
899 }
900 AuthMethod::PublicKey => {
901 if pos >= payload.len() {
902 return Err(SshError::InvalidPacket);
903 }
904 let has_signature = payload[pos] != 0;
905 pos += 1;
906 let algorithm = read_ssh_string(payload, &mut pos)?;
907 let public_key = read_ssh_string(payload, &mut pos)?;
908 let signature = if has_signature {
909 read_ssh_string(payload, &mut pos)?
910 } else {
911 Vec::new()
912 };
913 AuthMethodData::PublicKey {
914 has_signature,
915 algorithm,
916 public_key,
917 signature,
918 }
919 }
920 AuthMethod::KeyboardInteractive => AuthMethodData::None,
921 };
922
923 Ok(UserauthRequest {
924 username,
925 service_name,
926 method,
927 method_data,
928 })
929}
930
931pub(crate) fn encode_userauth_failure(methods: &[AuthMethod], partial_success: bool) -> Vec<u8> {
933 let mut name_list = Vec::new();
934 for (i, method) in methods.iter().enumerate() {
935 if i > 0 {
936 name_list.push(b',');
937 }
938 name_list.extend_from_slice(method.as_str().as_bytes());
939 }
940
941 let mut buf = Vec::with_capacity(8 + name_list.len());
942 buf.push(SSH_MSG_USERAUTH_FAILURE);
943 buf.extend_from_slice(&(name_list.len() as u32).to_be_bytes());
944 buf.extend_from_slice(&name_list);
945 buf.push(if partial_success { 1 } else { 0 });
946 buf
947}
948
949pub(crate) fn encode_userauth_success() -> Vec<u8> {
951 vec![SSH_MSG_USERAUTH_SUCCESS]
952}
953
954pub(crate) fn encode_userauth_pk_ok(algorithm: &[u8], public_key: &[u8]) -> Vec<u8> {
956 let mut buf = Vec::with_capacity(9 + algorithm.len() + public_key.len());
957 buf.push(SSH_MSG_USERAUTH_PK_OK);
958 write_ssh_string(&mut buf, algorithm);
959 write_ssh_string(&mut buf, public_key);
960 buf
961}
962
963pub(crate) fn encode_banner(message: &str) -> Vec<u8> {
965 let msg_bytes = message.as_bytes();
966 let mut buf = Vec::with_capacity(9 + msg_bytes.len());
967 buf.push(SSH_MSG_USERAUTH_BANNER);
968 write_ssh_string(&mut buf, msg_bytes);
969 write_ssh_string(&mut buf, b"");
971 buf
972}
973
974#[derive(Debug, Clone, Copy, PartialEq, Eq)]
980pub enum ChannelType {
981 Session,
983 DirectTcpip,
985 ForwardedTcpip,
987}
988
989impl ChannelType {
990 pub(crate) fn as_str(&self) -> &'static str {
991 match self {
992 Self::Session => "session",
993 Self::DirectTcpip => "direct-tcpip",
994 Self::ForwardedTcpip => "forwarded-tcpip",
995 }
996 }
997
998 pub(crate) fn from_bytes(data: &[u8]) -> Option<Self> {
999 match data {
1000 b"session" => Some(Self::Session),
1001 b"direct-tcpip" => Some(Self::DirectTcpip),
1002 b"forwarded-tcpip" => Some(Self::ForwardedTcpip),
1003 _ => None,
1004 }
1005 }
1006}
1007
1008#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1010pub enum ChannelState {
1011 Opening,
1013 Open,
1015 EofSent,
1017 EofReceived,
1019 CloseSent,
1021 CloseReceived,
1023 Closed,
1025}
1026
1027#[derive(Debug, Clone, PartialEq, Eq)]
1029pub struct Channel {
1030 pub local_id: u32,
1032 pub remote_id: u32,
1034 pub channel_type: ChannelType,
1036 pub state: ChannelState,
1038 pub local_window: u32,
1040 pub remote_window: u32,
1042 pub local_max_packet: u32,
1044 pub remote_max_packet: u32,
1046 pub pty_allocated: bool,
1048 pub pty_info: Option<PtyInfo>,
1050 pub session_started: bool,
1052 pub env_vars: Vec<(Vec<u8>, Vec<u8>)>,
1054 pub eof_received: bool,
1056 pub eof_sent: bool,
1058}
1059
1060impl Channel {
1061 pub fn new(local_id: u32, channel_type: ChannelType) -> Self {
1063 Self {
1064 local_id,
1065 remote_id: 0,
1066 channel_type,
1067 state: ChannelState::Opening,
1068 local_window: DEFAULT_WINDOW_SIZE,
1069 remote_window: 0,
1070 local_max_packet: MAX_CHANNEL_DATA_SIZE,
1071 remote_max_packet: 0,
1072 pty_allocated: false,
1073 pty_info: None,
1074 session_started: false,
1075 env_vars: Vec::new(),
1076 eof_received: false,
1077 eof_sent: false,
1078 }
1079 }
1080
1081 pub(crate) fn confirm(&mut self, remote_id: u32, remote_window: u32, remote_max_packet: u32) {
1083 self.remote_id = remote_id;
1084 self.remote_window = remote_window;
1085 self.remote_max_packet = remote_max_packet;
1086 self.state = ChannelState::Open;
1087 }
1088
1089 pub(crate) fn consume_send_window(&mut self, amount: u32) -> bool {
1091 if self.remote_window >= amount {
1092 self.remote_window -= amount;
1093 true
1094 } else {
1095 false
1096 }
1097 }
1098
1099 pub(crate) fn consume_recv_window(&mut self, amount: u32) -> bool {
1101 if self.local_window >= amount {
1102 self.local_window -= amount;
1103 true
1104 } else {
1105 false
1106 }
1107 }
1108
1109 pub(crate) fn adjust_local_window(&mut self, increment: u32) {
1111 self.local_window = self.local_window.saturating_add(increment);
1112 }
1113
1114 pub(crate) fn adjust_remote_window(&mut self, increment: u32) {
1116 self.remote_window = self.remote_window.saturating_add(increment);
1117 }
1118
1119 pub(crate) fn mark_eof_received(&mut self) {
1121 self.eof_received = true;
1122 if self.state == ChannelState::Open {
1123 self.state = ChannelState::EofReceived;
1124 }
1125 }
1126
1127 pub(crate) fn mark_eof_sent(&mut self) {
1129 self.eof_sent = true;
1130 if self.state == ChannelState::Open {
1131 self.state = ChannelState::EofSent;
1132 }
1133 }
1134
1135 pub(crate) fn close(&mut self) {
1137 self.state = ChannelState::Closed;
1138 }
1139}
1140
1141#[derive(Debug, Clone, PartialEq, Eq)]
1143pub struct ChannelTable {
1144 channels: BTreeMap<u32, Channel>,
1146 next_id: u32,
1148}
1149
1150impl Default for ChannelTable {
1151 fn default() -> Self {
1152 Self::new()
1153 }
1154}
1155
1156impl ChannelTable {
1157 pub fn new() -> Self {
1158 Self {
1159 channels: BTreeMap::new(),
1160 next_id: 0,
1161 }
1162 }
1163
1164 pub(crate) fn open(&mut self, channel_type: ChannelType) -> Result<u32, SshError> {
1166 let id = self.next_id;
1167 self.next_id = self.next_id.wrapping_add(1);
1168 let channel = Channel::new(id, channel_type);
1169 self.channels.insert(id, channel);
1170 Ok(id)
1171 }
1172
1173 pub(crate) fn get(&self, local_id: u32) -> Option<&Channel> {
1175 self.channels.get(&local_id)
1176 }
1177
1178 pub(crate) fn get_mut(&mut self, local_id: u32) -> Option<&mut Channel> {
1180 self.channels.get_mut(&local_id)
1181 }
1182
1183 pub(crate) fn remove(&mut self, local_id: u32) -> Option<Channel> {
1185 self.channels.remove(&local_id)
1186 }
1187
1188 pub(crate) fn count(&self) -> usize {
1190 self.channels.len()
1191 }
1192
1193 pub(crate) fn contains(&self, local_id: u32) -> bool {
1195 self.channels.contains_key(&local_id)
1196 }
1197}
1198
1199pub(crate) fn encode_channel_open(
1201 channel_type: ChannelType,
1202 sender_channel: u32,
1203 initial_window: u32,
1204 max_packet: u32,
1205) -> Vec<u8> {
1206 let type_str = channel_type.as_str().as_bytes();
1207 let mut buf = Vec::with_capacity(20 + type_str.len());
1208 buf.push(SSH_MSG_CHANNEL_OPEN);
1209 write_ssh_string(&mut buf, type_str);
1210 buf.extend_from_slice(&sender_channel.to_be_bytes());
1211 buf.extend_from_slice(&initial_window.to_be_bytes());
1212 buf.extend_from_slice(&max_packet.to_be_bytes());
1213 buf
1214}
1215
1216pub(crate) fn parse_channel_open(payload: &[u8]) -> Result<(ChannelType, u32, u32, u32), SshError> {
1218 if payload.is_empty() || payload[0] != SSH_MSG_CHANNEL_OPEN {
1219 return Err(SshError::InvalidPacket);
1220 }
1221 let mut pos = 1;
1222 let type_name = read_ssh_string(payload, &mut pos)?;
1223 let channel_type = ChannelType::from_bytes(&type_name).ok_or(SshError::InvalidChannelType)?;
1224
1225 if pos + 12 > payload.len() {
1226 return Err(SshError::InvalidPacket);
1227 }
1228 let sender_channel = read_u32(payload, &mut pos)?;
1229 let initial_window = read_u32(payload, &mut pos)?;
1230 let max_packet = read_u32(payload, &mut pos)?;
1231
1232 Ok((channel_type, sender_channel, initial_window, max_packet))
1233}
1234
1235pub(crate) fn encode_channel_open_confirmation(
1237 recipient_channel: u32,
1238 sender_channel: u32,
1239 initial_window: u32,
1240 max_packet: u32,
1241) -> Vec<u8> {
1242 let mut buf = Vec::with_capacity(17);
1243 buf.push(SSH_MSG_CHANNEL_OPEN_CONFIRMATION);
1244 buf.extend_from_slice(&recipient_channel.to_be_bytes());
1245 buf.extend_from_slice(&sender_channel.to_be_bytes());
1246 buf.extend_from_slice(&initial_window.to_be_bytes());
1247 buf.extend_from_slice(&max_packet.to_be_bytes());
1248 buf
1249}
1250
1251pub(crate) fn encode_channel_open_failure(
1253 recipient_channel: u32,
1254 reason_code: u32,
1255 description: &str,
1256) -> Vec<u8> {
1257 let desc_bytes = description.as_bytes();
1258 let mut buf = Vec::with_capacity(17 + desc_bytes.len());
1259 buf.push(SSH_MSG_CHANNEL_OPEN_FAILURE);
1260 buf.extend_from_slice(&recipient_channel.to_be_bytes());
1261 buf.extend_from_slice(&reason_code.to_be_bytes());
1262 write_ssh_string(&mut buf, desc_bytes);
1263 write_ssh_string(&mut buf, b"");
1265 buf
1266}
1267
1268pub(crate) fn encode_channel_data(recipient_channel: u32, data: &[u8]) -> Vec<u8> {
1270 let mut buf = Vec::with_capacity(9 + data.len());
1271 buf.push(SSH_MSG_CHANNEL_DATA);
1272 buf.extend_from_slice(&recipient_channel.to_be_bytes());
1273 write_ssh_string(&mut buf, data);
1274 buf
1275}
1276
1277pub(crate) fn parse_channel_data(payload: &[u8]) -> Result<(u32, Vec<u8>), SshError> {
1279 if payload.is_empty() || payload[0] != SSH_MSG_CHANNEL_DATA {
1280 return Err(SshError::InvalidPacket);
1281 }
1282 let mut pos = 1;
1283 let recipient_channel = read_u32(payload, &mut pos)?;
1284 let data = read_ssh_string(payload, &mut pos)?;
1285 Ok((recipient_channel, data))
1286}
1287
1288pub(crate) fn encode_channel_extended_data(
1290 recipient_channel: u32,
1291 data_type: u32,
1292 data: &[u8],
1293) -> Vec<u8> {
1294 let mut buf = Vec::with_capacity(13 + data.len());
1295 buf.push(SSH_MSG_CHANNEL_EXTENDED_DATA);
1296 buf.extend_from_slice(&recipient_channel.to_be_bytes());
1297 buf.extend_from_slice(&data_type.to_be_bytes());
1298 write_ssh_string(&mut buf, data);
1299 buf
1300}
1301
1302pub(crate) fn encode_window_adjust(recipient_channel: u32, bytes_to_add: u32) -> Vec<u8> {
1304 let mut buf = Vec::with_capacity(9);
1305 buf.push(SSH_MSG_CHANNEL_WINDOW_ADJUST);
1306 buf.extend_from_slice(&recipient_channel.to_be_bytes());
1307 buf.extend_from_slice(&bytes_to_add.to_be_bytes());
1308 buf
1309}
1310
1311pub(crate) fn parse_window_adjust(payload: &[u8]) -> Result<(u32, u32), SshError> {
1313 if payload.is_empty() || payload[0] != SSH_MSG_CHANNEL_WINDOW_ADJUST {
1314 return Err(SshError::InvalidPacket);
1315 }
1316 let mut pos = 1;
1317 let recipient_channel = read_u32(payload, &mut pos)?;
1318 let bytes_to_add = read_u32(payload, &mut pos)?;
1319 Ok((recipient_channel, bytes_to_add))
1320}
1321
1322pub(crate) fn encode_channel_eof(recipient_channel: u32) -> Vec<u8> {
1324 let mut buf = Vec::with_capacity(5);
1325 buf.push(SSH_MSG_CHANNEL_EOF);
1326 buf.extend_from_slice(&recipient_channel.to_be_bytes());
1327 buf
1328}
1329
1330pub(crate) fn encode_channel_close(recipient_channel: u32) -> Vec<u8> {
1332 let mut buf = Vec::with_capacity(5);
1333 buf.push(SSH_MSG_CHANNEL_CLOSE);
1334 buf.extend_from_slice(&recipient_channel.to_be_bytes());
1335 buf
1336}
1337
1338pub(crate) fn encode_channel_request(
1340 recipient_channel: u32,
1341 request_type: &str,
1342 want_reply: bool,
1343 data: &[u8],
1344) -> Vec<u8> {
1345 let type_bytes = request_type.as_bytes();
1346 let mut buf = Vec::with_capacity(10 + type_bytes.len() + data.len());
1347 buf.push(SSH_MSG_CHANNEL_REQUEST);
1348 buf.extend_from_slice(&recipient_channel.to_be_bytes());
1349 write_ssh_string(&mut buf, type_bytes);
1350 buf.push(if want_reply { 1 } else { 0 });
1351 buf.extend_from_slice(data);
1352 buf
1353}
1354
1355#[derive(Debug, Clone, PartialEq, Eq)]
1357pub struct ChannelRequest {
1358 pub recipient_channel: u32,
1360 pub request_type: Vec<u8>,
1362 pub want_reply: bool,
1364 pub data: Vec<u8>,
1366}
1367
1368pub(crate) fn parse_channel_request(payload: &[u8]) -> Result<ChannelRequest, SshError> {
1370 if payload.is_empty() || payload[0] != SSH_MSG_CHANNEL_REQUEST {
1371 return Err(SshError::InvalidPacket);
1372 }
1373 let mut pos = 1;
1374 let recipient_channel = read_u32(payload, &mut pos)?;
1375 let request_type = read_ssh_string(payload, &mut pos)?;
1376 if pos >= payload.len() {
1377 return Err(SshError::InvalidPacket);
1378 }
1379 let want_reply = payload[pos] != 0;
1380 pos += 1;
1381 let data = if pos < payload.len() {
1382 payload[pos..].to_vec()
1383 } else {
1384 Vec::new()
1385 };
1386
1387 Ok(ChannelRequest {
1388 recipient_channel,
1389 request_type,
1390 want_reply,
1391 data,
1392 })
1393}
1394
1395#[derive(Debug, Clone, PartialEq, Eq)]
1401pub struct PtyInfo {
1402 pub term_type: Vec<u8>,
1404 pub width_cols: u32,
1406 pub height_rows: u32,
1408 pub width_pixels: u32,
1410 pub height_pixels: u32,
1412 pub terminal_modes: Vec<u8>,
1414}
1415
1416impl PtyInfo {
1417 pub(crate) fn parse(data: &[u8]) -> Result<Self, SshError> {
1419 let mut pos = 0;
1420 let term_type = read_ssh_string(data, &mut pos)?;
1421 let width_cols = read_u32(data, &mut pos)?;
1422 let height_rows = read_u32(data, &mut pos)?;
1423 let width_pixels = read_u32(data, &mut pos)?;
1424 let height_pixels = read_u32(data, &mut pos)?;
1425 let terminal_modes = read_ssh_string(data, &mut pos)?;
1426
1427 Ok(Self {
1428 term_type,
1429 width_cols,
1430 height_rows,
1431 width_pixels,
1432 height_pixels,
1433 terminal_modes,
1434 })
1435 }
1436
1437 pub(crate) fn encode(&self) -> Vec<u8> {
1439 let mut buf = Vec::with_capacity(32 + self.term_type.len() + self.terminal_modes.len());
1440 write_ssh_string(&mut buf, &self.term_type);
1441 buf.extend_from_slice(&self.width_cols.to_be_bytes());
1442 buf.extend_from_slice(&self.height_rows.to_be_bytes());
1443 buf.extend_from_slice(&self.width_pixels.to_be_bytes());
1444 buf.extend_from_slice(&self.height_pixels.to_be_bytes());
1445 write_ssh_string(&mut buf, &self.terminal_modes);
1446 buf
1447 }
1448}
1449
1450#[derive(Debug, Clone, PartialEq, Eq)]
1452pub struct ExecRequest {
1453 pub command: Vec<u8>,
1455}
1456
1457impl ExecRequest {
1458 pub(crate) fn parse(data: &[u8]) -> Result<Self, SshError> {
1459 let mut pos = 0;
1460 let command = read_ssh_string(data, &mut pos)?;
1461 Ok(Self { command })
1462 }
1463}
1464
1465#[derive(Debug, Clone, PartialEq, Eq)]
1467pub struct EnvRequest {
1468 pub name: Vec<u8>,
1470 pub value: Vec<u8>,
1472}
1473
1474impl EnvRequest {
1475 pub(crate) fn parse(data: &[u8]) -> Result<Self, SshError> {
1476 let mut pos = 0;
1477 let name = read_ssh_string(data, &mut pos)?;
1478 let value = read_ssh_string(data, &mut pos)?;
1479 Ok(Self { name, value })
1480 }
1481}
1482
1483#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1485pub struct ExitStatus {
1486 pub code: u32,
1487}
1488
1489impl ExitStatus {
1490 pub(crate) fn encode(&self) -> Vec<u8> {
1491 self.code.to_be_bytes().to_vec()
1492 }
1493
1494 pub(crate) fn parse(data: &[u8]) -> Result<Self, SshError> {
1495 if data.len() < 4 {
1496 return Err(SshError::InvalidPacket);
1497 }
1498 let code = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
1499 Ok(Self { code })
1500 }
1501}
1502
1503#[derive(Debug, Clone, PartialEq, Eq)]
1505pub struct ExitSignal {
1506 pub signal_name: Vec<u8>,
1508 pub core_dumped: bool,
1510 pub error_message: Vec<u8>,
1512 pub language_tag: Vec<u8>,
1514}
1515
1516impl ExitSignal {
1517 pub(crate) fn encode(&self) -> Vec<u8> {
1518 let mut buf = Vec::with_capacity(
1519 16 + self.signal_name.len() + self.error_message.len() + self.language_tag.len(),
1520 );
1521 write_ssh_string(&mut buf, &self.signal_name);
1522 buf.push(if self.core_dumped { 1 } else { 0 });
1523 write_ssh_string(&mut buf, &self.error_message);
1524 write_ssh_string(&mut buf, &self.language_tag);
1525 buf
1526 }
1527
1528 pub(crate) fn parse(data: &[u8]) -> Result<Self, SshError> {
1529 let mut pos = 0;
1530 let signal_name = read_ssh_string(data, &mut pos)?;
1531 if pos >= data.len() {
1532 return Err(SshError::InvalidPacket);
1533 }
1534 let core_dumped = data[pos] != 0;
1535 pos += 1;
1536 let error_message = read_ssh_string(data, &mut pos)?;
1537 let language_tag = read_ssh_string(data, &mut pos)?;
1538 Ok(Self {
1539 signal_name,
1540 core_dumped,
1541 error_message,
1542 language_tag,
1543 })
1544 }
1545}
1546
1547#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1549pub enum ShellSessionState {
1550 Idle,
1552 PtyAllocated,
1554 Running,
1556 Exited,
1558}
1559
1560#[derive(Debug, Clone, PartialEq, Eq)]
1562pub struct ShellSession {
1563 pub channel_id: u32,
1565 pub state: ShellSessionState,
1567 pub pty: Option<PtyInfo>,
1569 pub command: Option<Vec<u8>>,
1571 pub environment: Vec<(Vec<u8>, Vec<u8>)>,
1573 pub exit_code: Option<u32>,
1575 pub exit_signal: Option<Vec<u8>>,
1577}
1578
1579impl ShellSession {
1580 pub fn new(channel_id: u32) -> Self {
1582 Self {
1583 channel_id,
1584 state: ShellSessionState::Idle,
1585 pty: None,
1586 command: None,
1587 environment: Vec::new(),
1588 exit_code: None,
1589 exit_signal: None,
1590 }
1591 }
1592
1593 pub(crate) fn allocate_pty(&mut self, pty_info: PtyInfo) -> Result<(), SshError> {
1595 if self.state != ShellSessionState::Idle {
1596 return Err(SshError::InvalidState);
1597 }
1598 self.pty = Some(pty_info);
1599 self.state = ShellSessionState::PtyAllocated;
1600 Ok(())
1601 }
1602
1603 pub(crate) fn start_shell(&mut self) -> Result<(), SshError> {
1605 match self.state {
1606 ShellSessionState::Idle | ShellSessionState::PtyAllocated => {
1607 self.state = ShellSessionState::Running;
1608 Ok(())
1609 }
1610 _ => Err(SshError::InvalidState),
1611 }
1612 }
1613
1614 pub fn start_exec(&mut self, command: Vec<u8>) -> Result<(), SshError> {
1616 match self.state {
1617 ShellSessionState::Idle | ShellSessionState::PtyAllocated => {
1618 self.command = Some(command);
1619 self.state = ShellSessionState::Running;
1620 Ok(())
1621 }
1622 _ => Err(SshError::InvalidState),
1623 }
1624 }
1625
1626 pub(crate) fn set_env(&mut self, name: Vec<u8>, value: Vec<u8>) {
1628 self.environment.push((name, value));
1629 }
1630
1631 pub(crate) fn mark_exited(&mut self, code: u32) {
1633 self.exit_code = Some(code);
1634 self.state = ShellSessionState::Exited;
1635 }
1636
1637 pub(crate) fn mark_signaled(&mut self, signal: Vec<u8>) {
1639 self.exit_signal = Some(signal);
1640 self.state = ShellSessionState::Exited;
1641 }
1642}
1643
1644#[derive(Debug, Clone, PartialEq, Eq)]
1650pub struct HostKeyPair {
1651 pub public_key: [u8; ED25519_PUB_LEN],
1653 pub private_key: [u8; 64],
1655}
1656
1657impl HostKeyPair {
1658 pub fn new(public_key: [u8; 32], private_key: [u8; 64]) -> Self {
1660 Self {
1661 public_key,
1662 private_key,
1663 }
1664 }
1665
1666 pub(crate) fn encode_public_key(&self) -> Vec<u8> {
1668 let mut buf = Vec::with_capacity(51);
1669 write_ssh_string(&mut buf, b"ssh-ed25519");
1670 write_ssh_string(&mut buf, &self.public_key);
1671 buf
1672 }
1673}
1674
1675#[derive(Debug, Clone, PartialEq, Eq)]
1677pub struct SshSession {
1678 pub session_id: u32,
1680 pub state: SessionState,
1682 pub auth: AuthState,
1684 pub kex: KexState,
1686 pub transport_keys: TransportKeys,
1688 pub algorithms: NegotiatedAlgorithms,
1690 pub channels: ChannelTable,
1692 pub shell_sessions: BTreeMap<u32, ShellSession>,
1694 pub session_hash: [u8; 32],
1696 pub client_version: Vec<u8>,
1698 pub server_version: Vec<u8>,
1700 pub client_kexinit: Vec<u8>,
1702 pub server_kexinit: Vec<u8>,
1704 pub send_seq: u32,
1706 pub recv_seq: u32,
1708}
1709
1710impl SshSession {
1711 pub fn new(session_id: u32) -> Self {
1713 Self {
1714 session_id,
1715 state: SessionState::VersionExchange,
1716 auth: AuthState::new(),
1717 kex: KexState::new(),
1718 transport_keys: TransportKeys::new(),
1719 algorithms: NegotiatedAlgorithms::new(),
1720 channels: ChannelTable::new(),
1721 shell_sessions: BTreeMap::new(),
1722 session_hash: [0u8; 32],
1723 client_version: Vec::new(),
1724 server_version: SSH_VERSION_STRING[..SSH_VERSION_STRING.len() - 2].to_vec(),
1725 client_kexinit: Vec::new(),
1726 server_kexinit: Vec::new(),
1727 send_seq: 0,
1728 recv_seq: 0,
1729 }
1730 }
1731
1732 pub(crate) fn process_version(&mut self, data: &[u8]) -> Result<SessionState, SshError> {
1734 if self.state != SessionState::VersionExchange {
1735 return Err(SshError::InvalidState);
1736 }
1737
1738 let version = VersionInfo::parse(data).ok_or(SshError::VersionMismatch)?;
1739 if &version.protocol_version != b"2.0" {
1740 return Err(SshError::VersionMismatch);
1741 }
1742
1743 self.client_version = data.to_vec();
1744 self.state = SessionState::KeyExchange;
1745 Ok(self.state)
1746 }
1747
1748 pub(crate) fn begin_key_exchange(&mut self) -> Result<(), SshError> {
1750 if self.state != SessionState::KeyExchange {
1751 return Err(SshError::InvalidState);
1752 }
1753 Ok(())
1754 }
1755
1756 pub(crate) fn process_newkeys(&mut self) -> Result<SessionState, SshError> {
1758 if self.state != SessionState::KeyExchange && self.state != SessionState::NewKeysExpected {
1759 return Err(SshError::InvalidState);
1760 }
1761 self.state = SessionState::Authentication;
1762 Ok(self.state)
1763 }
1764
1765 pub(crate) fn authenticate_success(&mut self) -> Result<SessionState, SshError> {
1767 if self.state != SessionState::Authentication {
1768 return Err(SshError::InvalidState);
1769 }
1770 self.auth.mark_success();
1771 self.state = SessionState::Connected;
1772 Ok(self.state)
1773 }
1774
1775 pub(crate) fn disconnect(&mut self) {
1777 self.state = SessionState::Disconnected;
1778 }
1779
1780 pub(crate) fn handle_channel_request(
1782 &mut self,
1783 request: &ChannelRequest,
1784 ) -> Result<bool, SshError> {
1785 let channel = self
1786 .channels
1787 .get_mut(request.recipient_channel)
1788 .ok_or(SshError::ChannelNotFound)?;
1789
1790 match request.request_type.as_slice() {
1791 b"pty-req" => {
1792 let pty_info = PtyInfo::parse(&request.data)?;
1793 channel.pty_allocated = true;
1794 channel.pty_info = Some(pty_info.clone());
1795
1796 let session = self
1798 .shell_sessions
1799 .entry(request.recipient_channel)
1800 .or_insert_with(|| ShellSession::new(request.recipient_channel));
1801 session.allocate_pty(pty_info)?;
1802 Ok(true)
1803 }
1804 b"shell" => {
1805 channel.session_started = true;
1806 let session = self
1807 .shell_sessions
1808 .entry(request.recipient_channel)
1809 .or_insert_with(|| ShellSession::new(request.recipient_channel));
1810 session.start_shell()?;
1811 Ok(true)
1812 }
1813 b"exec" => {
1814 let exec_req = ExecRequest::parse(&request.data)?;
1815 channel.session_started = true;
1816 let session = self
1817 .shell_sessions
1818 .entry(request.recipient_channel)
1819 .or_insert_with(|| ShellSession::new(request.recipient_channel));
1820 session.start_exec(exec_req.command)?;
1821 Ok(true)
1822 }
1823 b"env" => {
1824 let env_req = EnvRequest::parse(&request.data)?;
1825 let session = self
1826 .shell_sessions
1827 .entry(request.recipient_channel)
1828 .or_insert_with(|| ShellSession::new(request.recipient_channel));
1829 session.set_env(env_req.name, env_req.value);
1830 Ok(true)
1831 }
1832 b"exit-status" => {
1833 let exit = ExitStatus::parse(&request.data)?;
1834 if let Some(session) = self.shell_sessions.get_mut(&request.recipient_channel) {
1835 session.mark_exited(exit.code);
1836 }
1837 Ok(true)
1838 }
1839 b"exit-signal" => {
1840 let signal = ExitSignal::parse(&request.data)?;
1841 if let Some(session) = self.shell_sessions.get_mut(&request.recipient_channel) {
1842 session.mark_signaled(signal.signal_name);
1843 }
1844 Ok(true)
1845 }
1846 _ => {
1847 Ok(false)
1849 }
1850 }
1851 }
1852
1853 pub(crate) fn next_send_seq(&mut self) -> u32 {
1855 let seq = self.send_seq;
1856 self.send_seq = self.send_seq.wrapping_add(1);
1857 seq
1858 }
1859
1860 pub(crate) fn next_recv_seq(&mut self) -> u32 {
1862 let seq = self.recv_seq;
1863 self.recv_seq = self.recv_seq.wrapping_add(1);
1864 seq
1865 }
1866}
1867
1868#[derive(Debug, Clone)]
1870pub struct SshServer {
1871 pub host_key: HostKeyPair,
1873 pub port: u16,
1875 pub max_sessions: usize,
1877 pub sessions: BTreeMap<u32, SshSession>,
1879 pub next_session_id: u32,
1881 pub banner: Option<String>,
1883}
1884
1885impl SshServer {
1886 pub fn new(host_key: HostKeyPair, port: u16) -> Self {
1888 Self {
1889 host_key,
1890 port,
1891 max_sessions: MAX_SESSIONS,
1892 sessions: BTreeMap::new(),
1893 next_session_id: 0,
1894 banner: None,
1895 }
1896 }
1897
1898 pub(crate) fn set_banner(&mut self, banner: String) {
1900 self.banner = Some(banner);
1901 }
1902
1903 pub(crate) fn accept_connection(&mut self) -> Result<u32, SshError> {
1905 if self.sessions.len() >= self.max_sessions {
1906 return Err(SshError::SessionLimitReached);
1907 }
1908 let id = self.next_session_id;
1909 self.next_session_id = self.next_session_id.wrapping_add(1);
1910 let session = SshSession::new(id);
1911 self.sessions.insert(id, session);
1912 Ok(id)
1913 }
1914
1915 pub(crate) fn get_session(&self, session_id: u32) -> Option<&SshSession> {
1917 self.sessions.get(&session_id)
1918 }
1919
1920 pub(crate) fn get_session_mut(&mut self, session_id: u32) -> Option<&mut SshSession> {
1922 self.sessions.get_mut(&session_id)
1923 }
1924
1925 pub(crate) fn remove_session(&mut self, session_id: u32) -> Option<SshSession> {
1927 self.sessions.remove(&session_id)
1928 }
1929
1930 pub(crate) fn active_sessions(&self) -> usize {
1932 self.sessions.len()
1933 }
1934}
1935
1936fn read_ssh_string(data: &[u8], pos: &mut usize) -> Result<Vec<u8>, SshError> {
1942 if *pos + 4 > data.len() {
1943 return Err(SshError::InvalidPacket);
1944 }
1945 let len =
1946 u32::from_be_bytes([data[*pos], data[*pos + 1], data[*pos + 2], data[*pos + 3]]) as usize;
1947 *pos += 4;
1948 if *pos + len > data.len() {
1949 return Err(SshError::InvalidPacket);
1950 }
1951 let result = data[*pos..*pos + len].to_vec();
1952 *pos += len;
1953 Ok(result)
1954}
1955
1956fn write_ssh_string(buf: &mut Vec<u8>, data: &[u8]) {
1958 buf.extend_from_slice(&(data.len() as u32).to_be_bytes());
1959 buf.extend_from_slice(data);
1960}
1961
1962fn read_u32(data: &[u8], pos: &mut usize) -> Result<u32, SshError> {
1964 if *pos + 4 > data.len() {
1965 return Err(SshError::InvalidPacket);
1966 }
1967 let val = u32::from_be_bytes([data[*pos], data[*pos + 1], data[*pos + 2], data[*pos + 3]]);
1968 *pos += 4;
1969 Ok(val)
1970}
1971
1972#[cfg(test)]
1977mod tests {
1978 #[allow(unused_imports)]
1979 use alloc::vec;
1980
1981 use super::*;
1982
1983 #[test]
1988 fn test_version_parse_basic() {
1989 let input = b"SSH-2.0-OpenSSH_9.0\r\n";
1990 let v = VersionInfo::parse(input).unwrap();
1991 assert_eq!(&v.protocol_version, b"2.0");
1992 assert_eq!(&v.software_version, b"OpenSSH_9.0");
1993 assert!(v.comment.is_empty());
1994 }
1995
1996 #[test]
1997 fn test_version_parse_with_comment() {
1998 let input = b"SSH-2.0-VeridianOS_1.0 custom comment\r\n";
1999 let v = VersionInfo::parse(input).unwrap();
2000 assert_eq!(&v.software_version, b"VeridianOS_1.0");
2001 assert_eq!(&v.comment, b"custom comment");
2002 }
2003
2004 #[test]
2005 fn test_version_parse_no_crlf() {
2006 let input = b"SSH-2.0-TestClient\n";
2007 let v = VersionInfo::parse(input).unwrap();
2008 assert_eq!(&v.software_version, b"TestClient");
2009 }
2010
2011 #[test]
2012 fn test_version_parse_invalid_prefix() {
2013 assert!(VersionInfo::parse(b"TLS-2.0-Client\r\n").is_none());
2014 }
2015
2016 #[test]
2017 fn test_version_parse_wrong_protocol() {
2018 assert!(VersionInfo::parse(b"SSH-1.0-OldClient\r\n").is_none());
2019 }
2020
2021 #[test]
2022 fn test_version_encode_roundtrip() {
2023 let input = b"SSH-2.0-TestServer\r\n";
2024 let v = VersionInfo::parse(input).unwrap();
2025 let encoded = v.encode();
2026 assert_eq!(&encoded, input);
2027 }
2028
2029 #[test]
2034 fn test_packet_encode_decode_roundtrip() {
2035 let payload = vec![SSH_MSG_IGNORE, 0x01, 0x02, 0x03];
2036 let packet = SshPacket::new(payload.clone());
2037 let encoded = packet.encode();
2038
2039 let (decoded, consumed) = SshPacket::decode(&encoded).unwrap();
2040 assert_eq!(consumed, encoded.len());
2041 assert_eq!(decoded.payload, payload);
2042 }
2043
2044 #[test]
2045 fn test_packet_minimum_padding() {
2046 let payload = vec![SSH_MSG_IGNORE];
2047 let packet = SshPacket::new(payload);
2048 assert!(packet.padding.len() >= MIN_PADDING);
2049 }
2050
2051 #[test]
2052 fn test_packet_alignment() {
2053 let payload = vec![0u8; 17];
2054 let packet = SshPacket::new(payload);
2055 let encoded = packet.encode();
2056 assert_eq!(encoded.len() % BLOCK_SIZE_CLEAR, 0);
2058 }
2059
2060 #[test]
2061 fn test_packet_decode_too_short() {
2062 let data = [0u8; 3];
2063 assert_eq!(SshPacket::decode(&data), Err(SshError::InvalidPacket));
2064 }
2065
2066 #[test]
2067 fn test_packet_decode_truncated() {
2068 let mut data = vec![0, 0, 0, 100, 4];
2070 data.extend_from_slice(&[0u8; 5]);
2071 assert_eq!(SshPacket::decode(&data), Err(SshError::InvalidPacket));
2072 }
2073
2074 #[test]
2075 fn test_packet_message_type() {
2076 let packet = SshPacket::new(vec![SSH_MSG_KEXINIT, 0, 0]);
2077 assert_eq!(packet.message_type(), Some(SSH_MSG_KEXINIT));
2078 }
2079
2080 #[test]
2081 fn test_packet_empty_payload_message_type() {
2082 let packet = SshPacket {
2083 payload: Vec::new(),
2084 padding: vec![0u8; 4],
2085 };
2086 assert_eq!(packet.message_type(), None);
2087 }
2088
2089 #[test]
2094 fn test_kexinit_encode_decode_roundtrip() {
2095 let cookie = [0xAA; 16];
2096 let msg = KexInitMessage::new_server(cookie);
2097 let encoded = msg.encode();
2098
2099 assert_eq!(encoded[0], SSH_MSG_KEXINIT);
2100 assert_eq!(&encoded[1..17], &cookie);
2101
2102 let decoded = KexInitMessage::decode(&encoded).unwrap();
2103 assert_eq!(decoded.cookie, cookie);
2104 assert_eq!(decoded.kex_algorithms, msg.kex_algorithms);
2105 assert_eq!(
2106 decoded.server_host_key_algorithms,
2107 msg.server_host_key_algorithms
2108 );
2109 assert!(!decoded.first_kex_packet_follows);
2110 }
2111
2112 #[test]
2113 fn test_kexinit_decode_wrong_type() {
2114 let data = vec![SSH_MSG_NEWKEYS; 20];
2115 assert_eq!(KexInitMessage::decode(&data), Err(SshError::InvalidPacket));
2116 }
2117
2118 #[test]
2119 fn test_kexinit_decode_too_short() {
2120 let data = vec![SSH_MSG_KEXINIT; 10];
2121 assert_eq!(KexInitMessage::decode(&data), Err(SshError::InvalidPacket));
2122 }
2123
2124 #[test]
2129 fn test_channel_lifecycle() {
2130 let mut table = ChannelTable::new();
2131
2132 let id = table.open(ChannelType::Session).unwrap();
2134 assert_eq!(id, 0);
2135 assert_eq!(table.count(), 1);
2136
2137 {
2139 let ch = table.get_mut(id).unwrap();
2140 assert_eq!(ch.state, ChannelState::Opening);
2141 ch.confirm(42, DEFAULT_WINDOW_SIZE, MAX_CHANNEL_DATA_SIZE);
2142 assert_eq!(ch.state, ChannelState::Open);
2143 assert_eq!(ch.remote_id, 42);
2144 }
2145
2146 {
2148 let ch = table.get_mut(id).unwrap();
2149 ch.mark_eof_received();
2150 assert_eq!(ch.state, ChannelState::EofReceived);
2151 }
2152
2153 {
2155 let ch = table.get_mut(id).unwrap();
2156 ch.close();
2157 assert_eq!(ch.state, ChannelState::Closed);
2158 }
2159
2160 let removed = table.remove(id);
2162 assert!(removed.is_some());
2163 assert_eq!(table.count(), 0);
2164 }
2165
2166 #[test]
2167 fn test_channel_open_multiple() {
2168 let mut table = ChannelTable::new();
2169 let id1 = table.open(ChannelType::Session).unwrap();
2170 let id2 = table.open(ChannelType::Session).unwrap();
2171 assert_ne!(id1, id2);
2172 assert_eq!(table.count(), 2);
2173 }
2174
2175 #[test]
2180 fn test_window_adjust() {
2181 let mut ch = Channel::new(0, ChannelType::Session);
2182 ch.confirm(1, 1024, 512);
2183
2184 assert!(ch.consume_send_window(512));
2185 assert_eq!(ch.remote_window, 512);
2186 assert!(ch.consume_send_window(512));
2187 assert_eq!(ch.remote_window, 0);
2188 assert!(!ch.consume_send_window(1));
2189
2190 ch.adjust_remote_window(2048);
2191 assert_eq!(ch.remote_window, 2048);
2192 }
2193
2194 #[test]
2195 fn test_window_adjust_saturation() {
2196 let mut ch = Channel::new(0, ChannelType::Session);
2197 ch.remote_window = u32::MAX - 10;
2198 ch.adjust_remote_window(100);
2199 assert_eq!(ch.remote_window, u32::MAX);
2200 }
2201
2202 #[test]
2203 fn test_local_window_consume() {
2204 let mut ch = Channel::new(0, ChannelType::Session);
2205 let initial = ch.local_window;
2206 assert!(ch.consume_recv_window(1024));
2207 assert_eq!(ch.local_window, initial - 1024);
2208
2209 ch.adjust_local_window(2048);
2210 assert_eq!(ch.local_window, initial - 1024 + 2048);
2211 }
2212
2213 #[test]
2218 fn test_window_adjust_encode_parse() {
2219 let encoded = encode_window_adjust(7, 65536);
2220 let (ch, bytes) = parse_window_adjust(&encoded).unwrap();
2221 assert_eq!(ch, 7);
2222 assert_eq!(bytes, 65536);
2223 }
2224
2225 #[test]
2230 fn test_parse_password_auth() {
2231 let mut payload = vec![SSH_MSG_USERAUTH_REQUEST];
2232 payload.extend_from_slice(&5u32.to_be_bytes());
2234 payload.extend_from_slice(b"alice");
2235 payload.extend_from_slice(&14u32.to_be_bytes());
2237 payload.extend_from_slice(b"ssh-connection");
2238 payload.extend_from_slice(&8u32.to_be_bytes());
2240 payload.extend_from_slice(b"password");
2241 payload.push(0);
2243 payload.extend_from_slice(&6u32.to_be_bytes());
2245 payload.extend_from_slice(b"secret");
2246
2247 let req = parse_userauth_request(&payload).unwrap();
2248 assert_eq!(&req.username, b"alice");
2249 assert_eq!(&req.service_name, b"ssh-connection");
2250 assert_eq!(req.method, AuthMethod::Password);
2251 match &req.method_data {
2252 AuthMethodData::Password { password } => assert_eq!(password, b"secret"),
2253 _ => panic!("Expected password auth data"),
2254 }
2255 }
2256
2257 #[test]
2258 fn test_parse_pubkey_auth_query() {
2259 let mut payload = vec![SSH_MSG_USERAUTH_REQUEST];
2260 payload.extend_from_slice(&3u32.to_be_bytes());
2262 payload.extend_from_slice(b"bob");
2263 payload.extend_from_slice(&14u32.to_be_bytes());
2265 payload.extend_from_slice(b"ssh-connection");
2266 payload.extend_from_slice(&9u32.to_be_bytes());
2268 payload.extend_from_slice(b"publickey");
2269 payload.push(0);
2271 payload.extend_from_slice(&11u32.to_be_bytes());
2273 payload.extend_from_slice(b"ssh-ed25519");
2274 let fake_key = [0x42u8; 32];
2276 payload.extend_from_slice(&32u32.to_be_bytes());
2277 payload.extend_from_slice(&fake_key);
2278
2279 let req = parse_userauth_request(&payload).unwrap();
2280 assert_eq!(req.method, AuthMethod::PublicKey);
2281 match &req.method_data {
2282 AuthMethodData::PublicKey {
2283 has_signature,
2284 algorithm,
2285 public_key,
2286 signature,
2287 } => {
2288 assert!(!(*has_signature));
2289 assert_eq!(algorithm, b"ssh-ed25519");
2290 assert_eq!(public_key.len(), 32);
2291 assert!(signature.is_empty());
2292 }
2293 _ => panic!("Expected public key auth data"),
2294 }
2295 }
2296
2297 #[test]
2302 fn test_session_state_transitions() {
2303 let mut session = SshSession::new(0);
2304 assert_eq!(session.state, SessionState::VersionExchange);
2305
2306 let new_state = session.process_version(b"SSH-2.0-TestClient\r\n").unwrap();
2308 assert_eq!(new_state, SessionState::KeyExchange);
2309
2310 let new_state = session.process_newkeys().unwrap();
2312 assert_eq!(new_state, SessionState::Authentication);
2313
2314 let new_state = session.authenticate_success().unwrap();
2316 assert_eq!(new_state, SessionState::Connected);
2317
2318 session.disconnect();
2320 assert_eq!(session.state, SessionState::Disconnected);
2321 }
2322
2323 #[test]
2324 fn test_session_invalid_state_transition() {
2325 let mut session = SshSession::new(0);
2326 assert_eq!(session.process_newkeys(), Err(SshError::InvalidState));
2328 }
2329
2330 #[test]
2331 fn test_session_version_mismatch() {
2332 let mut session = SshSession::new(0);
2333 assert_eq!(
2334 session.process_version(b"SSH-1.0-OldClient\r\n"),
2335 Err(SshError::VersionMismatch)
2336 );
2337 }
2338
2339 #[test]
2344 fn test_channel_data_encode_parse() {
2345 let data = b"Hello, SSH world!";
2346 let encoded = encode_channel_data(5, data);
2347 let (ch, parsed_data) = parse_channel_data(&encoded).unwrap();
2348 assert_eq!(ch, 5);
2349 assert_eq!(&parsed_data, data);
2350 }
2351
2352 #[test]
2353 fn test_channel_data_empty() {
2354 let encoded = encode_channel_data(0, b"");
2355 let (ch, parsed_data) = parse_channel_data(&encoded).unwrap();
2356 assert_eq!(ch, 0);
2357 assert!(parsed_data.is_empty());
2358 }
2359
2360 #[test]
2365 fn test_disconnect_encode() {
2366 let msg = encode_disconnect(SSH_DISCONNECT_BY_APPLICATION, "goodbye");
2367 assert_eq!(msg[0], SSH_MSG_DISCONNECT);
2368 let reason = u32::from_be_bytes([msg[1], msg[2], msg[3], msg[4]]);
2369 assert_eq!(reason, SSH_DISCONNECT_BY_APPLICATION);
2370 }
2371
2372 #[test]
2377 fn test_pty_request_parse() {
2378 let mut data = Vec::new();
2379 write_ssh_string(&mut data, b"xterm-256color");
2381 data.extend_from_slice(&80u32.to_be_bytes());
2383 data.extend_from_slice(&24u32.to_be_bytes());
2385 data.extend_from_slice(&640u32.to_be_bytes());
2387 data.extend_from_slice(&480u32.to_be_bytes());
2389 write_ssh_string(&mut data, b"");
2391
2392 let pty = PtyInfo::parse(&data).unwrap();
2393 assert_eq!(&pty.term_type, b"xterm-256color");
2394 assert_eq!(pty.width_cols, 80);
2395 assert_eq!(pty.height_rows, 24);
2396 assert_eq!(pty.width_pixels, 640);
2397 assert_eq!(pty.height_pixels, 480);
2398 }
2399
2400 #[test]
2401 fn test_pty_encode_roundtrip() {
2402 let pty = PtyInfo {
2403 term_type: b"vt100".to_vec(),
2404 width_cols: 132,
2405 height_rows: 43,
2406 width_pixels: 0,
2407 height_pixels: 0,
2408 terminal_modes: vec![0],
2409 };
2410 let encoded = pty.encode();
2411 let decoded = PtyInfo::parse(&encoded).unwrap();
2412 assert_eq!(decoded.term_type, pty.term_type);
2413 assert_eq!(decoded.width_cols, pty.width_cols);
2414 assert_eq!(decoded.height_rows, pty.height_rows);
2415 }
2416
2417 #[test]
2422 fn test_auth_state_attempts() {
2423 let mut auth = AuthState::new();
2424 assert!(!auth.exhausted());
2425
2426 for _ in 0..MAX_AUTH_ATTEMPTS - 1 {
2427 assert!(auth.record_failure());
2428 }
2429 assert!(!auth.record_failure());
2430 assert!(auth.exhausted());
2431 }
2432
2433 #[test]
2434 fn test_auth_failure_message() {
2435 let methods = [AuthMethod::Password, AuthMethod::PublicKey];
2436 let msg = encode_userauth_failure(&methods, false);
2437 assert_eq!(msg[0], SSH_MSG_USERAUTH_FAILURE);
2438 assert_eq!(*msg.last().unwrap(), 0);
2440 }
2441
2442 #[test]
2447 fn test_service_request_parse() {
2448 let mut payload = vec![SSH_MSG_SERVICE_REQUEST];
2449 payload.extend_from_slice(&12u32.to_be_bytes());
2450 payload.extend_from_slice(b"ssh-userauth");
2451 let name = parse_service_request(&payload).unwrap();
2452 assert_eq!(&name, b"ssh-userauth");
2453 }
2454
2455 #[test]
2460 fn test_channel_request_parse() {
2461 let encoded = encode_channel_request(3, "shell", true, b"");
2462 let req = parse_channel_request(&encoded).unwrap();
2463 assert_eq!(req.recipient_channel, 3);
2464 assert_eq!(&req.request_type, b"shell");
2465 assert!(req.want_reply);
2466 assert!(req.data.is_empty());
2467 }
2468
2469 #[test]
2470 fn test_exec_request_parse() {
2471 let mut exec_data = Vec::new();
2472 write_ssh_string(&mut exec_data, b"ls -la /tmp");
2473 let encoded = encode_channel_request(1, "exec", true, &exec_data);
2474 let req = parse_channel_request(&encoded).unwrap();
2475 assert_eq!(&req.request_type, b"exec");
2476
2477 let exec = ExecRequest::parse(&req.data).unwrap();
2478 assert_eq!(&exec.command, b"ls -la /tmp");
2479 }
2480
2481 #[test]
2486 fn test_channel_open_encode_parse() {
2487 let encoded = encode_channel_open(
2488 ChannelType::Session,
2489 0,
2490 DEFAULT_WINDOW_SIZE,
2491 MAX_CHANNEL_DATA_SIZE,
2492 );
2493 let (ct, sender, window, max_pkt) = parse_channel_open(&encoded).unwrap();
2494 assert_eq!(ct, ChannelType::Session);
2495 assert_eq!(sender, 0);
2496 assert_eq!(window, DEFAULT_WINDOW_SIZE);
2497 assert_eq!(max_pkt, MAX_CHANNEL_DATA_SIZE);
2498 }
2499
2500 #[test]
2505 fn test_shell_session_lifecycle() {
2506 let mut session = ShellSession::new(0);
2507 assert_eq!(session.state, ShellSessionState::Idle);
2508
2509 let pty = PtyInfo {
2510 term_type: b"xterm".to_vec(),
2511 width_cols: 80,
2512 height_rows: 24,
2513 width_pixels: 0,
2514 height_pixels: 0,
2515 terminal_modes: Vec::new(),
2516 };
2517 session.allocate_pty(pty).unwrap();
2518 assert_eq!(session.state, ShellSessionState::PtyAllocated);
2519
2520 session.start_shell().unwrap();
2521 assert_eq!(session.state, ShellSessionState::Running);
2522
2523 session.mark_exited(0);
2524 assert_eq!(session.state, ShellSessionState::Exited);
2525 assert_eq!(session.exit_code, Some(0));
2526 }
2527
2528 #[test]
2529 fn test_shell_session_exec() {
2530 let mut session = ShellSession::new(1);
2531 session.start_exec(b"uname -a".to_vec()).unwrap();
2532 assert_eq!(session.state, ShellSessionState::Running);
2533 assert_eq!(session.command.as_deref(), Some(b"uname -a".as_slice()));
2534 }
2535
2536 #[test]
2537 fn test_shell_session_invalid_transition() {
2538 let mut session = ShellSession::new(0);
2539 session.start_shell().unwrap();
2540 let pty = PtyInfo {
2542 term_type: b"vt100".to_vec(),
2543 width_cols: 80,
2544 height_rows: 24,
2545 width_pixels: 0,
2546 height_pixels: 0,
2547 terminal_modes: Vec::new(),
2548 };
2549 assert_eq!(session.allocate_pty(pty), Err(SshError::InvalidState));
2550 }
2551
2552 #[test]
2557 fn test_server_accept_connection() {
2558 let host_key = HostKeyPair::new([0x11; 32], [0x22; 64]);
2559 let mut server = SshServer::new(host_key, 22);
2560 let id = server.accept_connection().unwrap();
2561 assert_eq!(id, 0);
2562 assert_eq!(server.active_sessions(), 1);
2563
2564 let session = server.get_session(id).unwrap();
2565 assert_eq!(session.state, SessionState::VersionExchange);
2566 }
2567
2568 #[test]
2569 fn test_server_session_limit() {
2570 let host_key = HostKeyPair::new([0x11; 32], [0x22; 64]);
2571 let mut server = SshServer::new(host_key, 22);
2572 server.max_sessions = 2;
2573
2574 server.accept_connection().unwrap();
2575 server.accept_connection().unwrap();
2576 assert_eq!(
2577 server.accept_connection(),
2578 Err(SshError::SessionLimitReached)
2579 );
2580 }
2581
2582 #[test]
2587 fn test_exit_status_encode_parse() {
2588 let status = ExitStatus { code: 42 };
2589 let encoded = status.encode();
2590 let parsed = ExitStatus::parse(&encoded).unwrap();
2591 assert_eq!(parsed.code, 42);
2592 }
2593
2594 #[test]
2595 fn test_exit_signal_encode_parse() {
2596 let sig = ExitSignal {
2597 signal_name: b"TERM".to_vec(),
2598 core_dumped: false,
2599 error_message: b"terminated".to_vec(),
2600 language_tag: b"".to_vec(),
2601 };
2602 let encoded = sig.encode();
2603 let parsed = ExitSignal::parse(&encoded).unwrap();
2604 assert_eq!(&parsed.signal_name, b"TERM");
2605 assert!(!parsed.core_dumped);
2606 assert_eq!(&parsed.error_message, b"terminated");
2607 }
2608
2609 #[test]
2614 fn test_env_request_parse() {
2615 let mut data = Vec::new();
2616 write_ssh_string(&mut data, b"LANG");
2617 write_ssh_string(&mut data, b"en_US.UTF-8");
2618 let env = EnvRequest::parse(&data).unwrap();
2619 assert_eq!(&env.name, b"LANG");
2620 assert_eq!(&env.value, b"en_US.UTF-8");
2621 }
2622
2623 #[test]
2628 fn test_host_key_encode() {
2629 let hk = HostKeyPair::new([0xAA; 32], [0xBB; 64]);
2630 let encoded = hk.encode_public_key();
2631 let mut pos = 0;
2633 let algo = read_ssh_string(&encoded, &mut pos).unwrap();
2634 assert_eq!(&algo, b"ssh-ed25519");
2635 let key = read_ssh_string(&encoded, &mut pos).unwrap();
2636 assert_eq!(key.len(), 32);
2637 }
2638
2639 #[test]
2644 fn test_sequence_counters() {
2645 let mut session = SshSession::new(0);
2646 assert_eq!(session.next_send_seq(), 0);
2647 assert_eq!(session.next_send_seq(), 1);
2648 assert_eq!(session.next_recv_seq(), 0);
2649 assert_eq!(session.next_recv_seq(), 1);
2650 }
2651
2652 #[test]
2657 fn test_banner_encode() {
2658 let msg = encode_banner("Welcome to VeridianOS SSH\r\n");
2659 assert_eq!(msg[0], SSH_MSG_USERAUTH_BANNER);
2660 }
2661
2662 #[test]
2667 fn test_algorithm_from_str() {
2668 assert_eq!(
2669 AlgorithmId::parse_name("curve25519-sha256"),
2670 Some(AlgorithmId::Curve25519Sha256)
2671 );
2672 assert_eq!(
2673 AlgorithmId::parse_name("ssh-ed25519"),
2674 Some(AlgorithmId::SshEd25519)
2675 );
2676 assert_eq!(AlgorithmId::parse_name("unknown-algo"), None);
2677 }
2678
2679 #[test]
2680 fn test_algorithm_roundtrip() {
2681 let algos = [
2682 AlgorithmId::Curve25519Sha256,
2683 AlgorithmId::SshEd25519,
2684 AlgorithmId::Chacha20Poly1305,
2685 AlgorithmId::Aes256Ctr,
2686 AlgorithmId::HmacSha256,
2687 AlgorithmId::None,
2688 ];
2689 for algo in &algos {
2690 let name = algo.as_str();
2691 let parsed = AlgorithmId::parse_name(name).unwrap();
2692 assert_eq!(*algo, parsed);
2693 }
2694 }
2695}