From 67b03ef81415712c881beb3c8acab3514659cae5 Mon Sep 17 00:00:00 2001 From: hidnplayr Date: Mon, 2 Aug 2021 18:40:01 +0000 Subject: [PATCH] Big refactor: separate backend from frontend. Prepare for dynamically negotiated algorithms etc. New: RSA host authentication, use new con_get_input from console.lib to get escape codes from special keys, UTF8 to CP866 decoder, .. Bugfix: CTR counters. git-svn-id: svn://kolibrios.org@9106 a494cfbc-eb01-0410-851d-a64ba20cac60 --- programs/network/ssh/aes256-ctr.inc | 6 +- programs/network/ssh/blowfish-ctr.inc | 2 +- programs/network/ssh/dh_gex.inc | 361 ------ programs/network/ssh/encodings.inc | 292 +++++ programs/network/ssh/ssh.asm | 1060 ++++++----------- programs/network/ssh/ssh_transport.inc | 274 ----- programs/network/ssh/sshlib.inc | 155 +++ programs/network/ssh/sshlib_channel.inc | 87 ++ programs/network/ssh/sshlib_connection.inc | 396 ++++++ programs/network/ssh/sshlib_dh_gex.inc | 507 ++++++++ programs/network/ssh/sshlib_host.inc | 243 ++++ .../ssh/{mcodes.inc => sshlib_mcodes.inc} | 1 + programs/network/ssh/sshlib_transport.inc | 275 +++++ programs/network/ssh/sshlib_userauth.inc | 149 +++ 14 files changed, 2479 insertions(+), 1329 deletions(-) delete mode 100644 programs/network/ssh/dh_gex.inc create mode 100644 programs/network/ssh/encodings.inc delete mode 100644 programs/network/ssh/ssh_transport.inc create mode 100644 programs/network/ssh/sshlib.inc create mode 100644 programs/network/ssh/sshlib_channel.inc create mode 100644 programs/network/ssh/sshlib_connection.inc create mode 100644 programs/network/ssh/sshlib_dh_gex.inc create mode 100644 programs/network/ssh/sshlib_host.inc rename programs/network/ssh/{mcodes.inc => sshlib_mcodes.inc} (91%) create mode 100644 programs/network/ssh/sshlib_transport.inc create mode 100644 programs/network/ssh/sshlib_userauth.inc diff --git a/programs/network/ssh/aes256-ctr.inc b/programs/network/ssh/aes256-ctr.inc index 17e79a5db0..401159075a 100644 --- a/programs/network/ssh/aes256-ctr.inc +++ b/programs/network/ssh/aes256-ctr.inc @@ -16,12 +16,15 @@ ; along with this program. If not, see . struct aes256_ctr_context aes256_context + counter rb AES256_BLOCKSIZE output rb AES256_BLOCKSIZE ; counter after aes_crypt + ends proc aes256_ctr_init _counter + push ebx esi edi mcall 68, 12, sizeof.aes256_ctr_context @@ -34,6 +37,7 @@ proc aes256_ctr_init _counter pop edi esi ebx ret + endp @@ -84,7 +88,7 @@ proc aes256_ctr_crypt _ctx, _in, _out bswap ecx bswap edx - inc edx + adc edx, 1 adc ecx, 0 adc ebx, 0 adc eax, 0 diff --git a/programs/network/ssh/blowfish-ctr.inc b/programs/network/ssh/blowfish-ctr.inc index bce80e5173..504d2f97d3 100644 --- a/programs/network/ssh/blowfish-ctr.inc +++ b/programs/network/ssh/blowfish-ctr.inc @@ -84,7 +84,7 @@ proc blowfish_ctr_crypt _ctx, _in, _out bswap ecx bswap edx - inc edx + adc edx, 1 adc ecx, 0 adc ebx, 0 adc eax, 0 diff --git a/programs/network/ssh/dh_gex.inc b/programs/network/ssh/dh_gex.inc deleted file mode 100644 index cbd11e5053..0000000000 --- a/programs/network/ssh/dh_gex.inc +++ /dev/null @@ -1,361 +0,0 @@ -; dh_gex.inc - Diffie Hellman Group exchange -; -; Copyright (C) 2015-2021 Jeffrey Amelynck -; -; This program is free software: you can redistribute it and/or modify -; it under the terms of the GNU General Public License as published by -; the Free Software Foundation, either version 3 of the License, or -; (at your option) any later version. -; -; This program is distributed in the hope that it will be useful, -; but WITHOUT ANY WARRANTY; without even the implied warranty of -; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -; GNU General Public License for more details. -; -; You should have received a copy of the GNU General Public License -; along with this program. If not, see . - -; https://www.ietf.org/rfc/rfc4419.txt - -; TODO: dont convert mpints to little endian immediately. -; Or maybe even better, not at all. - -proc dh_gex - -locals - dh_f_big dd ? -endl - -;---------------------------------------------- -; >> Send Diffie-Hellman Group Exchange Request - - DEBUGF 2, "Sending GEX\n" - stdcall ssh_send_packet, con, ssh_gex_req, ssh_gex_req.length, 0 - cmp eax, -1 - je .socket_err - -;--------------------------------------------- -; << Parse Diffie-Hellman Group Exchange Group - - stdcall ssh_recv_packet, con, 0 - cmp eax, -1 - je .socket_err - - cmp [con.rx_buffer.message_code], SSH_MSG_KEX_DH_GEX_GROUP - jne proto_err - DEBUGF 2, "Received GEX group\n" - - mov esi, con.rx_buffer+sizeof.ssh_packet_header - DEBUGF 1, "DH modulus (p): " - stdcall mpint_to_little_endian, con.dh_p, esi - add esi, 4 - add esi, eax - stdcall mpint_print, con.dh_p - - DEBUGF 1, "DH base (g): " - stdcall mpint_to_little_endian, con.dh_g, esi - add esi, 4 - add esi, eax - stdcall mpint_print, con.dh_g - -;------------------------------------------- -; >> Send Diffie-Hellman Group Exchange Init - -; generate a random number x, where 1 < x < (p-1)/2 - mov edi, con.dh_x+4 - mov [con.dh_x], DH_PRIVATE_KEY_SIZE/8 - mov ecx, DH_PRIVATE_KEY_SIZE/8/4 - @@: - push ecx - call MBRandom - pop ecx - stosd - dec ecx - jnz @r - -; If the highest bit is set, add a zero byte - shl eax, 1 - jnc @f - mov byte[edi], 0 - inc dword[con.dh_x] - @@: - - DEBUGF 1, "DH x: " - stdcall mpint_print, con.dh_x - -; Compute e = g^x mod p - stdcall mpint_modexp, con.dh_e, con.dh_g, con.dh_x, con.dh_p - stdcall mpint_shrink, con.dh_e - - DEBUGF 1, "DH e: " - stdcall mpint_print, con.dh_e - -; Create group exchange init packet - mov edi, con.tx_buffer.message_code - mov al, SSH_MSG_KEX_DH_GEX_INIT - stosb - stdcall mpint_to_big_endian, edi, con.dh_e - - DEBUGF 2, "Sending GEX init\n" - mov ecx, dword[con.tx_buffer.message_code+1] - bswap ecx - add ecx, 5 - stdcall ssh_send_packet, con, con.tx_buffer.message_code, ecx, 0 - cmp eax, -1 - je .socket_err - -;--------------------------------------------- -; << Parse Diffie-Hellman Group Exchange Reply - - stdcall ssh_recv_packet, con, 0 - cmp eax, -1 - je .socket_err - - cmp [con.rx_buffer.message_code], SSH_MSG_KEX_DH_GEX_REPLY - jne .proto_err - - DEBUGF 2, "Received GEX Reply\n" - -;-------------------------------- -; HASH: string K_S, the host key - mov esi, con.rx_buffer+sizeof.ssh_packet_header - mov edx, [esi] - bswap edx - add edx, 4 - lea ebx, [esi+edx] - mov [dh_f_big], ebx - invoke sha256_update, con.temp_ctx, esi, edx - -;-------------------------------------------------------------------------- -; HASH: uint32 min, minimal size in bits of an acceptable group -; uint32 n, preferred size in bits of the group the server will send -; uint32 max, maximal size in bits of an acceptable group - invoke sha256_update, con.temp_ctx, ssh_gex_req+sizeof.ssh_packet_header-ssh_packet_header.message_code, 12 - -;---------------------------- -; HASH: mpint p, safe prime - stdcall mpint_shrink, con.dh_p - stdcall mpint_to_big_endian, con.mpint_tmp, con.dh_p - lea edx, [eax+4] - invoke sha256_update, con.temp_ctx, con.mpint_tmp, edx - -;---------------------------------------- -; HASH: mpint g, generator for subgroup - stdcall mpint_shrink, con.dh_g - stdcall mpint_to_big_endian, con.mpint_tmp, con.dh_g - lea edx, [eax+4] - invoke sha256_update, con.temp_ctx, con.mpint_tmp, edx - -;--------------------------------------------------- -; HASH: mpint e, exchange value sent by the client - mov esi, con.tx_buffer+sizeof.ssh_packet_header - mov edx, [esi] - bswap edx - add edx, 4 - invoke sha256_update, con.temp_ctx, esi, edx - -;--------------------------------------------------- -; HASH: mpint f, exchange value sent by the server - mov esi, [dh_f_big] - mov edx, [esi] - bswap edx - add edx, 4 - invoke sha256_update, con.temp_ctx, esi, edx - - stdcall mpint_to_little_endian, con.dh_f, [dh_f_big] - mov esi, [dh_f_big] - add esi, eax - add esi, 4 - DEBUGF 1, "DH f: " - stdcall mpint_print, con.dh_f - - stdcall mpint_to_little_endian, con.dh_signature, esi - DEBUGF 1, "DH signature: " - stdcall mpint_print, con.dh_signature - -;-------------------------------------- -; Calculate shared secret K = f^x mod p - stdcall mpint_modexp, con.rx_buffer, con.dh_f, con.dh_x, con.dh_p - stdcall mpint_shrink, con.rx_buffer - - DEBUGF 1, "DH K: " - stdcall mpint_print, con.rx_buffer - -; We always need it in big endian order, so store it as such. - stdcall mpint_to_big_endian, con.dh_K, con.rx_buffer - mov [con.dh_K_length], eax - -;----------------------------------- -; HASH: mpint K, the shared secret - mov edx, [con.dh_K_length] - add edx, 4 - invoke sha256_update, con.temp_ctx, con.dh_K, edx - -;------------------------------- -; Finalize the exchange hash (H) - invoke sha256_final, con.temp_ctx - mov esi, con.temp_ctx.hash - mov edi, con.dh_H - mov ecx, SHA256_HASH_SIZE/4 - rep movsd - - DEBUGF 1, "Exchange hash H: " - stdcall dump_hex, con.dh_H, 8 - -; TODO: skip this block when re-keying - mov esi, con.dh_H - mov edi, con.session_id - mov ecx, SHA256_HASH_SIZE/4 - rep movsd - -;--------------- -; Calculate keys - -; First, calculate partial hash of K and H so we can re-use it for every key. - - invoke sha256_init, con.k_h_ctx - - mov edx, [con.dh_K_length] - add edx, 4 - invoke sha256_update, con.k_h_ctx, con.dh_K, edx - invoke sha256_update, con.k_h_ctx, con.dh_H, 32 - -;--------------------------------------------------------------- -; Initial IV client to server: HASH(K || H || "A" || session_id) - - mov esi, con.k_h_ctx - mov edi, con.temp_ctx - mov ecx, sizeof.crash_ctx/4 - rep movsd - mov [con.session_id_prefix], 'A' - invoke sha256_update, con.temp_ctx, con.session_id_prefix, 32+1 - invoke sha256_final, con.temp_ctx.hash - mov edi, con.tx_iv - mov esi, con.temp_ctx - mov ecx, SHA256_HASH_SIZE/4 - rep movsd - - DEBUGF 1, "Remote IV: " - stdcall dump_hex, con.tx_iv, 8 - -;--------------------------------------------------------------- -; Initial IV server to client: HASH(K || H || "B" || session_id) - - mov esi, con.k_h_ctx - mov edi, con.temp_ctx - mov ecx, sizeof.crash_ctx/4 - rep movsd - inc [con.session_id_prefix] - invoke sha256_update, con.temp_ctx, con.session_id_prefix, 32+1 - invoke sha256_final, con.temp_ctx - mov edi, con.rx_iv - mov esi, con.temp_ctx - mov ecx, SHA256_HASH_SIZE/4 - rep movsd - - DEBUGF 1, "Local IV: " - stdcall dump_hex, con.rx_iv, 8 - -;------------------------------------------------------------------- -; Encryption key client to server: HASH(K || H || "C" || session_id) - - mov esi, con.k_h_ctx - mov edi, con.temp_ctx - mov ecx, sizeof.crash_ctx/4 - rep movsd - inc [con.session_id_prefix] - invoke sha256_update, con.temp_ctx, con.session_id_prefix, 32+1 - invoke sha256_final, con.temp_ctx - mov edi, con.tx_enc_key - mov esi, con.temp_ctx - mov ecx, SHA256_HASH_SIZE/4 - rep movsd - - DEBUGF 1, "Remote key: " - stdcall dump_hex, con.tx_enc_key, 8 - -;------------------------------------------------------------------- -; Encryption key server to client: HASH(K || H || "D" || session_id) - - mov esi, con.k_h_ctx - mov edi, con.temp_ctx - mov ecx, sizeof.crash_ctx/4 - rep movsd - inc [con.session_id_prefix] - invoke sha256_update, con.temp_ctx, con.session_id_prefix, 32+1 - invoke sha256_final, con.temp_ctx - mov edi, con.rx_enc_key - mov esi, con.temp_ctx - mov ecx, SHA256_HASH_SIZE/4 - rep movsd - - DEBUGF 1, "Local key: " - stdcall dump_hex, con.rx_enc_key, 8 - -;------------------------------------------------------------------ -; Integrity key client to server: HASH(K || H || "E" || session_id) - - mov esi, con.k_h_ctx - mov edi, con.temp_ctx - mov ecx, sizeof.crash_ctx/4 - rep movsd - inc [con.session_id_prefix] - invoke sha256_update, con.temp_ctx, con.session_id_prefix, 32+1 - invoke sha256_final, con.temp_ctx - mov edi, con.tx_int_key - mov esi, con.temp_ctx - mov ecx, SHA256_HASH_SIZE/4 - rep movsd - - DEBUGF 1, "Remote Integrity key: " - stdcall dump_hex, con.tx_int_key, 8 - -;------------------------------------------------------------------ -; Integrity key server to client: HASH(K || H || "F" || session_id) - - mov esi, con.k_h_ctx - mov edi, con.temp_ctx - mov ecx, sizeof.crash_ctx/4 - rep movsd - inc [con.session_id_prefix] - invoke sha256_update, con.temp_ctx, con.session_id_prefix, 32+1 - invoke sha256_final, con.temp_ctx - mov edi, con.rx_int_key - mov esi, con.temp_ctx - mov ecx, SHA256_HASH_SIZE/4 - rep movsd - - DEBUGF 1, "Local Integrity key: " - stdcall dump_hex, con.rx_int_key, 8 - -;------------------------------------- -; << Parse Diffie-Hellman New Keys MSG - - stdcall ssh_recv_packet, con, 0 - cmp eax, -1 - je .socket_err - - cmp [con.rx_buffer.message_code], SSH_MSG_NEWKEYS - jne .proto_err - - DEBUGF 2, "Received New Keys\n" - -;------------------------------- -; >> Reply with New Keys message - - stdcall ssh_send_packet, con, ssh_new_keys, ssh_new_keys.length, 0 - - xor eax, eax - ret - - .socket_err: - DEBUGF 3, "Socket error during key exchange!\n" - mov eax, 1 - ret - - .proto_err: - DEBUGF 3, "Protocol error during key exchange!\n" - mov eax, 2 - ret - -endp diff --git a/programs/network/ssh/encodings.inc b/programs/network/ssh/encodings.inc new file mode 100644 index 0000000000..69ed715467 --- /dev/null +++ b/programs/network/ssh/encodings.inc @@ -0,0 +1,292 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; ;; +;; Copyright (C) KolibriOS team 2004-2013. All rights reserved. ;; +;; Distributed under terms of the GNU General Public License ;; +;; ;; +;; Written by CleverMouse ;; +;; ;; +;; GNU GENERAL PUBLIC LICENSE ;; +;; Version 2, June 1991 ;; +;; ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +uglobal + +utf8_bytes_rest dd ? ; bytes rest in current UTF8 sequence +utf8_char dd ? ; first bits of current UTF8 character + +endg + + +;get_next_byte: +;; Load next byte from the packet, translating to cp866 if necessary +;; At input esi = pointer to data, edx = limit of data +;; Output is either (translated) byte in al with CF set or CF cleared. +; mov eax, [encoding] +; jmp [get_byte_table+eax*4] +; +;get_byte_cp866: +; cmp esi, edx +; jae .nothing +; lodsb +;.nothing: +; ret +; +;get_byte_cp1251: +; cmp esi, edx +; jae .nothing +; lodsb +; cmp al, 0x80 +; jb @f +; and eax, 0x7F +; mov al, [cp1251_table+eax] +;@@: +; stc +;.nothing: +; ret + +get_byte_utf8: +; UTF8 decoding is slightly complicated. +; One character can occupy one or more bytes. +; The boundary in packets theoretically can be anywhere in data, +; so this procedure keeps internal state between calls and handles +; one byte at a time, looping until character is read or packet is over. +; Globally, there are two distinct tasks: decode byte sequence to unicode char +; and convert this unicode char to our base encoding (that is cp866). +; 1. Check that there are data. + cmp esi, edx + jae .nothing +; 2. Load byte. + lodsb + movzx ecx, al +; 3. Bytes in an UTF8 sequence can be of any of three types. +; If most significant bit is cleared, sequence is one byte and usual ASCII char. +; First byte of a sequence must be 11xxxxxx, other bytes are 10yyyyyy. + and al, 0xC0 + jns .single_byte + jp .first_byte +; 4. This byte is not first in UTF8 sequence. +; 4a. Check that the sequence was started. If no, it is invalid byte +; and we simply ignore it. + cmp [utf8_bytes_rest], 0 + jz get_byte_utf8 +; 4b. Otherwise, it is really next byte and it gives some more bits of char. + mov eax, [utf8_char] + shl eax, 6 + lea eax, [eax+ecx-0x80] +; 4c. Decrement number of bytes rest in the sequence. +; If it goes to zero, character is read, so return it. + dec [utf8_bytes_rest] + jz .got_char + mov [utf8_char], eax + jmp get_byte_utf8 +; 5. If the byte is first in UTF8 sequence, calculate the number of leading 1s +; - it equals total number of bytes in the sequence; some other bits rest for +; leading bits in the character. +.first_byte: + mov eax, -1 +@@: + inc eax + add cl, cl + js @b + mov [utf8_bytes_rest], eax + xchg eax, ecx + inc ecx + shr al, cl + mov [utf8_char], eax + jmp get_byte_utf8 +; 6. If the byte is ASCII char, it is the character. +.single_byte: + xchg eax, ecx +.got_char: +; We got the character, now abandon a possible sequence in progress. + and [utf8_bytes_rest], 0 +; Now second task. The unicode character is in eax, and now we shall convert it +; to cp866. + cmp eax, 0x80 + jb .done +; 0x410-0x43F -> 0x80-0xAF, 0x440-0x44F -> 0xE0-0xEF, 0x401 -> 0xF0, 0x451 -> 0xF1 + cmp eax, 0x401 + jz .YO + cmp eax, 0x451 + jz .yo + cmp eax, 0x410 + jb .unrecognized + cmp eax, 0x440 + jb .part1 + cmp eax, 0x450 + jb .part2 + cmp eax, 0x25a0 + jae .unrecognized + sub eax, 0x2500 + jb .unrecognized + mov al, [cp866_boxes+eax] + ret +.part1: + sub al, 0x10-0x80 +.nothing: +.done: + ret +.part2: + sub al, (0x40-0xE0) and 0xFF + ret +.unrecognized: + mov al, '?' + stc + ret +.YO: + mov al, 0xF0 + stc + ret +.yo: + mov al, 0xF1 + stc + ret + + + +;recode_to_cp866: +; rep movsb +; ret +; +;recode_to_cp1251: +; xor eax, eax +; jecxz .nothing +; .loop: +; lodsb +; cmp al,0x80 +; jb @f +; mov al, [cp866_table-0x80+eax] +; @@: stosb +; loop .loop +; .nothing: +; ret + +recode_to_utf8: + jecxz .nothing + .loop: + lodsb + cmp al, 0x80 + jb .single_byte + and eax, 0x7F + mov ax, [utf8_table+eax*2] + stosw + loop .loop + ret + .single_byte: + stosb + loop .loop + .nothing: + ret + +;recode: +; mov eax, [encoding] +; jmp [recode_proc+eax*4] + + + +;encoding dd UTF8 +;recode_proc dd recode_to_cp866, recode_to_cp1251, recode_to_utf8 +;get_byte_table dd get_byte_cp866, get_byte_cp1251, get_byte_utf8 + + +;cp1251_table: +; db '?','?','?','?','?','?','?','?' , '?','?','?','?','?','?','?','?' ; 8 +; db '?','?','?','?','?',$F9,'?','?' , '?','?','?','?','?','?','?','?' ; 9 +; db '?',$F6,$F7,'?',$FD,'?','?','?' , $F0,'?',$F2,'?','?','?','?',$F4 ; A +; db $F8,'?','?','?','?','?','?',$FA , $F1,$FC,$F3,'?','?','?','?',$F5 ; B +; db $80,$81,$82,$83,$84,$85,$86,$87 , $88,$89,$8A,$8B,$8C,$8D,$8E,$8F ; C +; db $90,$91,$92,$93,$94,$95,$96,$97 , $98,$99,$9A,$9B,$9C,$9D,$9E,$9F ; D +; db $A0,$A1,$A2,$A3,$A4,$A5,$A6,$A7 , $A8,$A9,$AA,$AB,$AC,$AD,$AE,$AF ; E +; db $E0,$E1,$E2,$E3,$E4,$E5,$E6,$E7 , $E8,$E9,$EA,$EB,$EC,$ED,$EE,$EF ; F + +; 0 1 2 3 4 5 6 7 8 9 A B C D E F + +utf8_table: + times 80h dw 0x98C3 ; default placeholder + +; 0x80-0xAF -> 0x90D0-0xBFD0 +repeat 0x30 + store byte 0xD0 at utf8_table+2*(%-1) + store byte 0x90+%-1 at utf8_table+2*%-1 +end repeat + +; 0xE0-0xEF -> 0x80D1-0x8FD1 +repeat 0x10 + store byte 0xD1 at utf8_table+2*(0xE0-0x80+%-1) + store byte 0x80+%-1 at utf8_table+2*(0xE0-0x80+%)-1 +end repeat + +; 0xF0 -> 0x81D0, 0xF1 -> 0x91D1 + store dword 0x91D181D0 at utf8_table+2*(0xF0-0x80) + +;cp866_table: +; db $C0,$C1,$C2,$C3,$C4,$C5,$C6,$C7 , $C8,$C9,$CA,$CB,$CC,$CD,$CE,$CF ; 8 +; db $D0,$D1,$D2,$D3,$D4,$D5,$D6,$D7 , $D8,$D9,$DA,$DB,$DC,$DD,$DE,$DF ; 9 +; db $E0,$E1,$E2,$E3,$E4,$E5,$E6,$E7 , $E8,$E9,$EA,$EB,$EC,$ED,$EE,$EF ; A +; db '?','?','?','?','?','?','?','?' , '?','?','?','?','?','?','?','?' ; B +; db '?','?','?','?','?','?','?','?' , '?','?','?','?','?','?','?','?' ; C +; db '?','?','?','?','?','?','?','?' , '?','?','?','?','?','?','?','?' ; D +; db $F0,$F1,$F2,$F3,$F4,$F5,$F6,$F7 , $F8,$F9,$FA,$FB,$FC,$FD,$FE,$FF ; E +; db $A8,$B8,$AA,$BA,$AF,$BF,$A1,$A2 , $B0,$95,$B7,'?',$B9,$A4,'?','?' ; F + +; 0 1 2 3 4 5 6 7 8 9 A B C D E F + + +; Codepoints for 0xB0-0xDF, unicode offset 0x2500 +cp866_boxes: + times 0xA0 db '?' + + store byte 0xB0 at cp866_boxes+0x91 + store byte 0xB1 at cp866_boxes+0x92 + store byte 0xB2 at cp866_boxes+0x93 + store byte 0xB3 at cp866_boxes+0x02 + store byte 0xB4 at cp866_boxes+0x24 + store byte 0xB5 at cp866_boxes+0x61 + store byte 0xB6 at cp866_boxes+0x62 + store byte 0xB7 at cp866_boxes+0x56 + + store byte 0xB8 at cp866_boxes+0x55 + store byte 0xB9 at cp866_boxes+0x63 + store byte 0xBA at cp866_boxes+0x51 + store byte 0xBB at cp866_boxes+0x57 + store byte 0xBC at cp866_boxes+0x5D + store byte 0xBD at cp866_boxes+0x5C + store byte 0xBE at cp866_boxes+0x5B + store byte 0xBF at cp866_boxes+0x10 + + store byte 0xC0 at cp866_boxes+0x14 + store byte 0xC1 at cp866_boxes+0x34 + store byte 0xC2 at cp866_boxes+0x2C + store byte 0xC3 at cp866_boxes+0x1C + store byte 0xC4 at cp866_boxes+0x00 + store byte 0xC5 at cp866_boxes+0x3C + store byte 0xC6 at cp866_boxes+0x5E + store byte 0xC7 at cp866_boxes+0x5F + + store byte 0xC8 at cp866_boxes+0x5A + store byte 0xC9 at cp866_boxes+0x54 + store byte 0xCA at cp866_boxes+0x69 + store byte 0xCB at cp866_boxes+0x66 + store byte 0xCC at cp866_boxes+0x60 + store byte 0xCD at cp866_boxes+0x50 + store byte 0xCE at cp866_boxes+0x6C + store byte 0xCF at cp866_boxes+0x67 + + store byte 0xD0 at cp866_boxes+0x68 + store byte 0xD1 at cp866_boxes+0x64 + store byte 0xD2 at cp866_boxes+0x65 + store byte 0xD3 at cp866_boxes+0x59 + store byte 0xD4 at cp866_boxes+0x58 + store byte 0xD5 at cp866_boxes+0x52 + store byte 0xD6 at cp866_boxes+0x53 + store byte 0xD7 at cp866_boxes+0x6B + + store byte 0xD8 at cp866_boxes+0x6A + store byte 0xD9 at cp866_boxes+0x18 + store byte 0xDA at cp866_boxes+0x0C + store byte 0xDB at cp866_boxes+0x88 + store byte 0xDC at cp866_boxes+0x84 + store byte 0xDD at cp866_boxes+0x8C + store byte 0xDE at cp866_boxes+0x90 + store byte 0xDF at cp866_boxes+0x80 diff --git a/programs/network/ssh/ssh.asm b/programs/network/ssh/ssh.asm index e5dbaefc6f..ffc3664635 100644 --- a/programs/network/ssh/ssh.asm +++ b/programs/network/ssh/ssh.asm @@ -17,13 +17,19 @@ format binary as "" -__DEBUG__ = 1 -__DEBUG_LEVEL__ = 3 ; 1: Everything, including sinsitive information, 2: Debugging, 3: Errors only +__DEBUG__ = 1 +__DEBUG_LEVEL__ = 2 ; 1: Everything, including sensitive information, 2: Debugging, 3: Errors only -BUFFERSIZE = 4096 -MAX_BITS = 8192 +BUFFERSIZE = 64*1024 ; Must be at least 32K according rfc4253#section-6.1 +PACKETSIZE = 32*1024 ; Must be at least 32K according rfc4253#section-6.1 +MAX_BITS = 8192 DH_PRIVATE_KEY_SIZE = 256 +MAX_INPUT_LENGTH = 255 ;;; WHAT WAS THIS AGAIN ?! +MAX_USERNAME_LENGTH = 256 +MAX_PASSWORD_LENGTH = 256 +MAX_HOSTNAME_LENGTH = 4096 +MAX_PUBLIC_KEY_SIZE = 4096 use32 @@ -45,27 +51,6 @@ include '../../debug-fdo.inc' include '../../network.inc' include '../../develop/libraries/libcrash/trunk/libcrash.inc' -include 'mcodes.inc' -include 'ssh_transport.inc' - -include 'dh_gex.inc' - -include 'mpint.inc' -include 'seed.inc' -include 'random.inc' - -include 'aes256.inc' -include 'aes256-ctr.inc' -include 'aes256-cbc.inc' - -include 'blowfish.inc' -include 'blowfish-ctr.inc' -include 'blowfish-cbc.inc' - -include 'hmac_sha256.inc' -include 'hmac_sha1.inc' -include 'hmac_md5.inc' - ; macros for network byte order macro dd_n op { dd 0 or (((op) and 0FF000000h) shr 24) or \ @@ -79,6 +64,15 @@ macro dw_n op { (((op) and 000FFh) shl 8) } +macro str string { + local .start, .stop + + dd_n (.stop-.start) + + .start db string + .stop: +} + proc dump_hex _ptr, _length if __DEBUG_LEVEL__ <= 1 pushad @@ -97,92 +91,42 @@ end if ret endp -struct ssh_connection +macro DEBUGM l, s, m { +if __DEBUG__ + DEBUGF l, s + if l >=__DEBUG_LEVEL__ + stdcall mpint_print, m + end if +end if +} -; Connection +include 'mpint.inc' +include 'seed.inc' +include 'random.inc' - hostname rb 1024 +include 'aes256.inc' +include 'aes256-ctr.inc' +include 'aes256-cbc.inc' - socketnum dd ? +include 'blowfish.inc' +include 'blowfish-ctr.inc' +include 'blowfish-cbc.inc' - sockaddr dw ? ; Address family - port dw ? - ip dd ? - rb 10 +include 'hmac_sha256.inc' +include 'hmac_sha1.inc' +include 'hmac_md5.inc' -; Encryption/Decryption +include 'sshlib.inc' - rx_crypt_proc dd ? - tx_crypt_proc dd ? - rx_crypt_ctx_ptr dd ? - tx_crypt_ctx_ptr dd ? - rx_crypt_blocksize dd ? - tx_crypt_blocksize dd ? +include 'sshlib_mcodes.inc' +include 'sshlib_transport.inc' +include 'sshlib_connection.inc' +include 'sshlib_dh_gex.inc' +include 'sshlib_host.inc' +include 'sshlib_channel.inc' +include 'sshlib_userauth.inc' -; Padding - -; rx_padsize dd ? ; = Max(8, rx_crypt_blocksize) - tx_pad_size dd ? ; = Max(8, tx_crypt_blocksize) - tx_pad_proc dd ? - -; Message authentication - - rx_mac_proc dd ? - tx_mac_proc dd ? - rx_mac_ctx hmac_sha256_context - tx_mac_ctx hmac_sha256_context - rx_mac_length dd ? - tx_mac_length dd ? - -; Buffers - - rx_seq dd ? ; Packet sequence number for MAC - rx_buffer ssh_packet_header - rb BUFFERSIZE-sizeof.ssh_packet_header - - tx_seq dd ? ; Packet sequence number for MAC - tx_buffer ssh_packet_header - rb BUFFERSIZE-sizeof.ssh_packet_header - - send_data dw ? - -; Output from key exchange - dh_K dd ? ; Shared Secret (Big endian) - rb MAX_BITS/8 - dh_K_length dd ? ; Length in little endian - - dh_H rb 32 ; Exchange Hash - session_id_prefix db ? - session_id rb 32 - rx_iv rb 32 ; Rx initialisation vector - tx_iv rb 32 ; Tx initialisation vector - rx_enc_key rb 32 ; Rx encryption key - tx_enc_key rb 32 ; Tx encryption key - rx_int_key rb 32 ; Rx integrity key - tx_int_key rb 32 ; Tx integrity key - -; Diffie Hellman - dh_p dd ? - rb MAX_BITS/8 - dh_g dd ? - rb MAX_BITS/8 - dh_x dd ? - rb MAX_BITS/8 - dh_e dd ? - rb MAX_BITS/8 - dh_f dd ? - rb MAX_BITS/8 - - dh_signature dd ? - rb MAX_BITS/8 - - temp_ctx crash_ctx - k_h_ctx crash_ctx - - mpint_tmp dd ? - rb MAX_BITS/8 - -ends +include 'encodings.inc' ; Unfortunately, we dont have UTF-8 capable console yet :( start: mcall 68, 11 ; Init heap @@ -190,7 +134,7 @@ start: DEBUGF 2, "SSH: Loading libraries\n" stdcall dll.Load, @IMPORT test eax, eax - jnz exit + jnz main.fail DEBUGF 2, "SSH: Init PRNG\n" call create_seed @@ -198,715 +142,447 @@ start: DEBUGF 2, "SSH: Init Console\n" invoke con_start, 1 - invoke con_init, 80, 25, 80, 25, title + invoke con_init, 80, 25, 800, 250, title -; Check for parameters TODO -; cmp byte[params], 0 -; jne resolve + cmp byte[params], 0 + jne main.connect main: invoke con_cls ; Welcome user - invoke con_write_asciiz, str1 - -prompt: -; write prompt + invoke con_write_asciiz, str1a + .prompt: + invoke con_write_asciiz, str1b +; Reset window title + invoke con_set_title, title +; Write prompt invoke con_write_asciiz, str2 ; read string - mov esi, con.hostname - invoke con_gets, esi, 256 + mov esi, params + invoke con_gets, esi, MAX_HOSTNAME_LENGTH ; check for exit test eax, eax - jz done + jz .done cmp byte[esi], 10 - jz done + jz .done -resolve: - mov [con.sockaddr], AF_INET4 - mov [con.port], 22 shl 8 + .connect: + stdcall sshlib_connect, ssh_con, params + cmp eax, 0 + jg .prompt + jl .error -; delete terminating '\n' - mov esi, con.hostname - @@: - lodsb - cmp al, ':' - je .do_port - cmp al, 0x20 - ja @r - mov byte[esi-1], 0 - jmp .done - - .do_port: - xor eax, eax - xor ebx, ebx - mov byte[esi-1], 0 - .portloop: - lodsb - cmp al, 0x20 - jbe .port_done - sub al, '0' - jb hostname_error - cmp al, 9 - ja hostname_error - lea ebx, [ebx*4+ebx] - shl ebx, 1 - add ebx, eax - jmp .portloop - - .port_done: - xchg bl, bh - mov [con.port], bx - - .done: - -; resolve name - push esp ; reserve stack place - push esp - invoke getaddrinfo, con.hostname, 0, 0 - pop esi -; test for error + .login: + mcall 68, 12, (MAX_USERNAME_LENGTH + MAX_PASSWORD_LENGTH) test eax, eax - jnz dns_error + jz .done ; ERR_NOMEM + mov esi, eax + lea edi, [eax + MAX_USERNAME_LENGTH] - invoke con_write_asciiz, str3 - invoke con_write_asciiz, con.hostname - -; write results - invoke con_write_asciiz, str8 - -; convert IP address to decimal notation - mov eax, [esi+addrinfo.ai_addr] - mov eax, [eax+sockaddr_in.sin_addr] - mov [con.ip], eax - invoke inet_ntoa, eax -; write result - invoke con_write_asciiz, eax -; free allocated memory - invoke freeaddrinfo, esi - - invoke con_write_asciiz, str9 - - mcall 40, EVM_STACK + EVM_KEY - -; Create socket - mcall socket, AF_INET4, SOCK_STREAM, 0 - cmp eax, -1 - jz socket_err - mov [con.socketnum], eax - -; Connect - DEBUGF 2, "Connecting to server\n" - mcall connect, [con.socketnum], con.sockaddr, 18 - test eax, eax - jnz socket_err - -; Start calculating hash - invoke sha256_init, con.temp_ctx -; HASH: string V_C, the client's version string (CR and NL excluded) - invoke sha256_update, con.temp_ctx, ssh_ident_ha, ssh_ident.length+4-2 - -; >> Send our identification string - DEBUGF 2, "Sending ID string\n" - mcall send, [con.socketnum], ssh_ident, ssh_ident.length, 0 - cmp eax, -1 - je socket_err - -; << Check protocol version of server - mcall recv, [con.socketnum], con.rx_buffer, BUFFERSIZE, 0 - cmp eax, -1 - je socket_err - - DEBUGF 2, "Received ID string\n" - cmp dword[con.rx_buffer], "SSH-" - jne proto_err - cmp dword[con.rx_buffer+4], "2.0-" - jne proto_err - -; HASH: string V_S, the server's version string (CR and NL excluded) - lea edx, [eax+2] - sub eax, 2 - bswap eax - mov dword[con.rx_buffer-4], eax - invoke sha256_update, con.temp_ctx, con.rx_buffer-4, edx - -; >> Key Exchange init - mov [con.rx_seq], 0 - mov [con.tx_seq], 0 - mov [con.rx_crypt_blocksize], 4 ; minimum blocksize - mov [con.tx_crypt_blocksize], 4 - mov [con.rx_crypt_proc], 0 - mov [con.tx_crypt_proc], 0 - mov [con.rx_mac_proc], 0 - mov [con.tx_mac_proc], 0 - mov [con.rx_mac_length], 0 - mov [con.tx_mac_length], 0 -; mov [con.rx_padsize], 8 ; minimum padsize - mov [con.tx_pad_size], 8 - mov [con.tx_pad_proc], padding_zero - - DEBUGF 2, "Sending KEX init\n" - mov edi, ssh_kex.cookie - call MBRandom - stosd - call MBRandom - stosd - call MBRandom - stosd - call MBRandom - stosd - stdcall ssh_send_packet, con, ssh_kex, ssh_kex.length, 0 - cmp eax, -1 - je socket_err - -; HASH: string I_C, the payload of the client's SSH_MSG_KEXINIT - mov eax, dword[con.tx_buffer+ssh_packet_header.packet_length] - bswap eax - movzx ebx, [con.tx_buffer+ssh_packet_header.padding_length] - sub eax, ebx - dec eax - lea edx, [eax+4] - bswap eax - mov dword[con.tx_buffer+1], eax - invoke sha256_update, con.temp_ctx, con.tx_buffer+1, edx - -; << Check key exchange init of server - stdcall ssh_recv_packet, con, 0 - cmp eax, -1 - je socket_err - - cmp [con.rx_buffer.message_code], SSH_MSG_KEXINIT - jne proto_err - DEBUGF 2, "Received KEX init\n" - - lea esi, [con.rx_buffer+sizeof.ssh_packet_header+16] - lodsd - bswap eax - DEBUGF 1, "kex_algorithms: %s\n", esi - add esi, eax - lodsd - bswap eax - DEBUGF 1, "server_host_key_algorithms: %s\n", esi - add esi, eax - lodsd - bswap eax - DEBUGF 1, "encryption_algorithms_client_to_server: %s\n", esi - add esi, eax - lodsd - bswap eax - DEBUGF 1, "encryption_algorithms_server_to_client: %s\n", esi - add esi, eax - lodsd - bswap eax - DEBUGF 1, "mac_algorithms_client_to_server: %s\n", esi - add esi, eax - lodsd - bswap eax - DEBUGF 1, "mac_algorithms_server_to_client: %s\n", esi - add esi, eax - lodsd - bswap eax - DEBUGF 1, "compression_algorithms_client_to_server: %s\n", esi - add esi, eax - lodsd - bswap eax - DEBUGF 1, "compression_algorithms_server_to_client: %s\n", esi - add esi, eax - lodsd - bswap eax - DEBUGF 1, "languages_client_to_server: %s\n", esi - add esi, eax - lodsd - bswap eax - DEBUGF 1, "languages_server_to_client: %s\n", esi - add esi, eax - lodsb - DEBUGF 1, "KEX First Packet Follows: %u\n", al - -; TODO: parse this structure and init procedures accordingly - -; HASH: string I_S, the payload of the servers's SSH_MSG_KEXINIT - mov eax, dword[con.rx_buffer+ssh_packet_header.packet_length] - movzx ebx, [con.rx_buffer+ssh_packet_header.padding_length] - sub eax, ebx - dec eax - lea edx, [eax+4] - bswap eax - mov dword[con.rx_buffer+sizeof.ssh_packet_header-5], eax - invoke sha256_update, con.temp_ctx, con.rx_buffer+sizeof.ssh_packet_header-5, edx - -; Exchange keys with the server - -; TODO: host verification - - stdcall dh_gex - test eax, eax - jnz exit - -; Set keys and initialize transport subroutines - - DEBUGF 2, "SSH: Setting encryption keys\n" - - stdcall aes256_ctr_init, con.rx_iv - mov [con.rx_crypt_ctx_ptr], eax - - stdcall aes256_set_encrypt_key, eax, con.rx_enc_key - mov [con.rx_crypt_proc], aes256_ctr_crypt - mov [con.rx_crypt_blocksize], AES256_BLOCKSIZE -; mov [con.rx_pad_size], AES256_BLOCKSIZE - - stdcall aes256_ctr_init, con.tx_iv - mov [con.tx_crypt_ctx_ptr], eax - - stdcall aes256_set_encrypt_key, eax, con.tx_enc_key - mov [con.tx_crypt_proc], aes256_ctr_crypt - mov [con.tx_crypt_blocksize], AES256_BLOCKSIZE - - mov [con.tx_pad_size], AES256_BLOCKSIZE - mov [con.tx_pad_proc], MBRandom - - stdcall hmac_sha256_setkey, con.rx_mac_ctx, con.rx_int_key, SHA256_HASH_SIZE - mov [con.rx_mac_proc], hmac_sha256 - mov [con.rx_mac_length], SHA256_HASH_SIZE - - stdcall hmac_sha256_setkey, con.tx_mac_ctx, con.tx_int_key, SHA256_HASH_SIZE - mov [con.tx_mac_proc], hmac_sha256 - mov [con.tx_mac_length], SHA256_HASH_SIZE - -; Re-seed RNG for padding bytes - call create_seed - call init_random - -; TODO: erase all keys from memory and free the memory - -; >> Request service (user-auth) - - DEBUGF 2, "SSH: Requesting service\n" - - stdcall ssh_send_packet, con, ssh_request_service, ssh_request_service.length, 0 - cmp eax, -1 - je socket_err - -; << Check for service acceptance - - stdcall ssh_msg_handler, con, 0 - cmp eax, -1 - je socket_err - - cmp [con.rx_buffer.message_code], SSH_MSG_SERVICE_ACCEPT - jne proto_err - -; >> Request user authentication - - DEBUGF 2, "SSH: User authentication\n" - - mcall 68, 12, 1024 ; FIXME - test eax, eax - jz done ; FIXME - mov edi, eax - mov ebx, eax - mov byte[edi], SSH_MSG_USERAUTH_REQUEST - inc edi - - ; Get username - add edi, 4 +; Get username invoke con_write_asciiz, str12 - invoke con_gets, edi, 256 ; FIXME + invoke con_gets, esi, MAX_USERNAME_LENGTH test eax, eax - jz done ; FIXME +;; jz .con_closed_must_clear - mov edx, eax - mov ecx, 256 - xor al, al - repne scasb - - dec edi ; \0 - dec edi ; \n - push edi - sub edi, edx - bswap edi - mov [edx-4], edi - pop edi - - mov dword[edi], 0x0e000000 ; 14 Bswapped - mov dword[edi+4], "ssh-" - mov dword[edi+8], "conn" - mov dword[edi+12], "ecti" - mov word[edi+16], "on" - add edi, 18 - - mov dword[edi], 0x08000000 ; 8 Bswapped - mov dword[edi+4], "pass" - mov dword[edi+8], "word" - - mov byte[edi+12], 0 ; bool - add edi, 13 - - ; Get password - add edi, 4 - invoke con_write_asciiz, str13 - push eax - invoke con_gets, edi, 256 ; FIXME +; Get password + invoke con_write_asciiz, str13a + invoke con_gets, edi, MAX_PASSWORD_LENGTH test eax, eax - jz done ; FIXME +;; jz .con_closed_must_clear + invoke con_write_asciiz, str13b - mov edx, eax - mov ecx, 256 - xor al, al - repne scasb - - dec edi ; \0 - dec edi ; \n - push edi - sub edi, edx - bswap edi - mov [edx-4], edi - pop edi - sub edi, ebx - - push ebx - stdcall ssh_send_packet, con, ebx, edi, 0 - - ; Clear used buffer and free - pop edx - mov edi, edx +; Authenticate + stdcall sshlib_userauth_password, ssh_con, esi, edi +; Clear and free username and password + .clear: push eax - mov ecx, 1024/4 ; FIXME + mov edx, edi xor eax, eax - rep stosd + mov ecx, (MAX_USERNAME_LENGTH + MAX_PASSWORD_LENGTH)/4 + rep stosd mcall 68, 13, edx pop eax - cmp eax, -1 - je socket_err + cmp eax, 0 + jg .login ; Authentication failed + jl .error ; An error occured - invoke con_write_asciiz, str14 +; Open a channel + stdcall sshlib_chan_open, ssh_con + cmp eax, 0 + jg .prompt ; Authentication failed + jl .error ; An error occured -; << Check for userauth acceptance - - stdcall ssh_msg_handler, con, 0 - cmp eax, -1 - je socket_err - - cmp [con.rx_buffer.message_code], SSH_MSG_USERAUTH_SUCCESS - jne proto_err - -; >> Open channel - - DEBUGF 2, "SSH: Open channel\n" - - stdcall ssh_send_packet, con, ssh_channel_open, ssh_channel_open.length, 0 - cmp eax, -1 - je socket_err - -; << Check for channel open confirmation - - stdcall ssh_msg_handler, con, 0 - cmp eax, -1 - je socket_err - - cmp [con.rx_buffer.message_code], SSH_MSG_CHANNEL_OPEN_CONFIRMATION - jne proto_err - -; >> Channel request: pty - - DEBUGF 2, "SSH: Request pty\n" - - stdcall ssh_send_packet, con, ssh_channel_request, ssh_channel_request.length, 0 - cmp eax, -1 - je socket_err - -; << Check for channel request confirmation - - stdcall ssh_msg_handler, con, 0 - cmp eax, -1 - je socket_err - - cmp [con.rx_buffer.message_code], SSH_MSG_CHANNEL_SUCCESS - jne proto_err - -; >> Channel request: shell - - DEBUGF 2, "SSH: Request shell\n" - - stdcall ssh_send_packet, con, ssh_shell_request, ssh_shell_request.length, 0 - cmp eax, -1 - je socket_err - -; << Check for channel request confirmation (FIXME: this may not be first packet!) - -; TODO -; -; stdcall ssh_msg_handler, con, 0 -; cmp eax, -1 -; je socket_err - -; cmp [con.rx_buffer.message_code], SSH_MSG_CHANNEL_SUCCESS -; jne proto_err - -; Launch network thread +; Start console input handler thread without deactivating the current window +; Get active window ID mcall 18, 7 push eax - mcall 51, 1, thread, mem - 2048 +; Create thread + mcall 51, 1, con_in_thread, mem - 2048 +; Activate window with given ID pop ecx mcall 18, 3 -mainloop: - call [con_get_flags] - test eax, 0x200 ; con window closed? - jnz exit + .loop: + invoke con_get_flags + test eax, 0x200 ; console window closed? + jnz .con_closed - stdcall ssh_msg_handler, con, 0 + stdcall sshlib_msg_handler, ssh_con, 0 cmp eax, 0 - jbe closed + jle .check_err - cmp [con.rx_buffer.message_code], SSH_MSG_CHANNEL_DATA + cmp [ssh_con.rx_buffer.message_code], SSH_MSG_CHANNEL_DATA jne .dump - mov eax, dword[con.rx_buffer.message_code+5] + mov eax, dword[ssh_con.rx_buffer.message_code+5] bswap eax DEBUGF 1, 'SSH: got %u bytes of data !\n', eax - lea esi, [con.rx_buffer.message_code+5+4] - mov ecx, eax - lea edi, [esi + eax] - mov byte [edi], 0 + lea esi, [ssh_con.rx_buffer.message_code+5+4] + lea edx, [esi+eax] + lea edi, [ssh_con.rx_buffer] + @@: + call get_byte_utf8 + stosb + cmp esi, edx + jb @r + xor al, al + stosb + + lea esi, [ssh_con.rx_buffer] + DEBUGF 3, 'SSH msg: %s\n', esi + invoke con_write_asciiz, esi - jmp mainloop + jmp .loop .dump: - lea esi, [con.rx_buffer] + DEBUGF 3, "SSH: Unsupported message: " + lea esi, [ssh_con.rx_buffer.message_code] mov ecx, eax pusha -@@: + @@: lodsb - DEBUGF 1, "%x ", eax:2 + DEBUGF 3, "%x ", eax:2 dec ecx jnz @r popa - DEBUGF 1, "\n" - jmp mainloop + DEBUGF 3, "\n" + jmp .loop + + .check_err: + jz .err_conn_closed + cmp ebx, EWOULDBLOCK + je .loop + jmp .err_sock + + .con_closed: + ; Send close message on the active channel + stdcall sshlib_send_packet, ssh_con, ssh_msg_channel_close, ssh_msg_channel_close.length, 0 + jmp .done + + .error: + +; TODO: proper cleanup after error + + cmp eax, SSHLIB_ERR_NOMEM + je .done + cmp eax, SSHLIB_ERR_SOCKET + je .err_sock + cmp eax, SSHLIB_ERR_PROTOCOL + je .err_proto + cmp eax, SSHLIB_ERR_HOSTNAME + je .err_hostname + cmp eax, SSHLIB_ERR_HKEY_VERIFY_FAIL + je .err_hostkey_fail + cmp eax, SSHLIB_ERR_HKEY_SIGNATURE + je .err_hostkey_signature + cmp eax, SSHLIB_ERR_HKEY_PUBLIC_KEY + je .err_hostkey + + jmp .done -proto_err: - mov eax, con.rx_buffer - int3 - - DEBUGF 3, "SSH: protocol error\n" + .err_proto: +; lea eax, [ssh_con.rx_buffer] +; int3 invoke con_write_asciiz, str7 - jmp prompt + jmp .prompt -socket_err: - DEBUGF 3, "SSH: socket error %d\n", ebx + .err_sock: invoke con_write_asciiz, str6 - jmp prompt -dns_error: - DEBUGF 3, "SSH: DNS error %d\n", eax - invoke con_write_asciiz, str5 - jmp prompt + mov eax, str14 + cmp ebx, ETIMEDOUT + je .err_sock_detail + mov eax, str15 + cmp ebx, ECONNREFUSED + je .err_sock_detail + mov eax, str16 + cmp ebx, ECONNRESET + je .err_sock_detail + mov eax, str17 + .err_sock_detail: + invoke con_write_asciiz, eax + jmp .prompt -hostname_error: + .err_hostname: invoke con_write_asciiz, str10 - jmp prompt + jmp .prompt -closed: + .err_conn_closed: invoke con_write_asciiz, str11 - jmp prompt + jmp .prompt -done: + .err_hostkey: + invoke con_write_asciiz, str19 + jmp .prompt + + .err_hostkey_signature: + invoke con_write_asciiz, str20 + jmp .prompt + + .err_hostkey_fail: + invoke con_write_asciiz, str21 + jmp .prompt + + .done: invoke con_exit, 1 -exit: + .exit: DEBUGF 3, "SSH: Exiting\n" - mcall close, [con.socketnum] + mcall close, [ssh_con.socketnum] + .fail: mcall -1 -thread: - mcall 40, 0 - .loop: - invoke con_getch2 - mov [ssh_channel_data+9], al - stdcall ssh_send_packet, con, ssh_channel_data, ssh_channel_data.length, MSG_DONTWAIT +proc sshlib_callback_connecting, con_ptr, connstring_sz - invoke con_get_flags - test eax, 0x200 ; con window closed? - jz .loop - mcall -1 + invoke con_write_asciiz, str3 + mov eax, [con_ptr] + lea eax, [eax+sshlib_connection.hostname_sz] + invoke con_write_asciiz, eax + invoke con_write_asciiz, str8 + invoke con_write_asciiz, [connstring_sz] + invoke con_write_asciiz, str9 - -; Handle common messages and return from specific ones -proc ssh_msg_handler, con, flags - - .recv: - stdcall ssh_recv_packet, [con], [flags] - cmp eax, -1 - je .ret - - cmp [con.rx_buffer.message_code], SSH_MSG_DISCONNECT - je .disc - cmp [con.rx_buffer.message_code], SSH_MSG_IGNORE - je .ign - cmp [con.rx_buffer.message_code], SSH_MSG_DEBUG - je .dbg - cmp [con.rx_buffer.message_code], SSH_MSG_GLOBAL_REQUEST - je .glob - - .ret: ret +endp + + +proc sshlib_callback_hostkey_problem, con_ptr, problem_type, hostkey_sz + + cmp [problem_type], SSHLIB_HOSTKEY_PROBLEM_UNKNOWN + je .unknown + cmp [problem_type], SSHLIB_HOSTKEY_PROBLEM_MISMATCH + je .mismatch - .disc: mov eax, -1 ret - .ign: - jmp .recv + .unknown: + invoke con_write_asciiz, str22 + jmp .ask - .dbg: - .glob: - ; TODO + .mismatch: + invoke con_write_asciiz, str23 +; jmp .ask + .ask: + ;;; TODO: print hostkey + invoke con_write_asciiz, str24 + .getansw: + invoke con_getch2 + or al, 0x20 ; convert to lowercase + cmp al, 'a' + je .accept + cmp al, 'c' + je .once + cmp al, 'x' + je .refuse + jmp .getansw - jmp .recv + .accept: + mov eax, SSHLIB_HOSTKEY_ACCEPT + ret + .once: + mov eax, SSHLIB_HOSTKEY_ONCE + ret + .refuse: + mov eax, SSHLIB_HOSTKEY_REFUSE + ret endp + + +align 16 +con_in_thread: + + .loop: +; TODO: check if channel is still open somehow + + invoke con_get_input, ssh_msg_channel_data.data, MAX_INPUT_LENGTH + test eax, eax + jz .no_input + + lea ecx, [eax + ssh_msg_channel_data.data - ssh_msg_channel_data] + bswap eax + mov [ssh_msg_channel_data.len], eax + stdcall sshlib_send_packet, ssh_con, ssh_msg_channel_data, ecx, 0 + cmp eax, 0 + jle .exit + + .no_input: + invoke con_get_flags + test eax, 0x200 ; con window closed? + jz .loop + + .exit: + mcall -1 + + ; data -title db 'Secure Shell',0 -str1 db 'SSH client for KolibriOS',10,10,\ - 'Please enter URL of SSH server (hostname:port)',10,10,0 -str2 db '> ',0 -str3 db 'Connecting to ',0 -str4 db 10,0 -str5 db 'Name resolution failed.',10,10,0 -str6 db 'A socket error occured.',10,10,0 -str7 db 'A protocol error occured.',10,10,0 -str8 db ' (',0 -str9 db ')',10,0 -str10 db 'Invalid hostname.',10,10,0 -str11 db 10,'Remote host closed the connection.',10,10,0 -str12 db 'Login as: ',0 -str13 db 'Password: ', 27, '[?25l', 27, '[30;40m', 0 -str14 db 10, 27, '[?25h', 27, '[0m', 0 +title db 'Secure Shell',0 +str1a db 'SSHv2 client for KolibriOS',10,0 +str1b db 10,'Please enter URL of SSH server (hostname:port)',10,0 +str2 db '> ',0 +str3 db 'Connecting to ',0 +str4 db 10,0 +str6 db 10, 27, '[2J',27,'[mA network error has occured.',10,0 +str7 db 10, 27, '[2J',27,'[mAn SSH protocol error has occured.',10,0 +str8 db ' (',0 +str9 db ')',10,0 +str10 db 'Host does not exist.',10,10,0 +str11 db 10, 27, '[2J',27,'[mThe remote host closed the connection.',10,0 +str12 db 'Login as: ',0 +str13a db 'Password: ', 27, '[?25l', 27, '[30;40m', 0 +str13b db 10, 27, '[?25h', 27, '[0m', 27, '[2J', 0 +str14 db 'The connection timed out',10,0 +str15 db 'The connection was refused',10,0 +str16 db 'The connection was reset',10,0 +str17 db 'No details available',10,0 +;str18 db 'User authentication failed',10,0;;;; +str19 db "The remote host's public key is invalid.", 10, 0 +str20 db "The remote host's signature is invalid.", 10, 0 +str21 db "The remote host failed to verify it's own public key.", 10, 0 +str22 db "The host key for the server was not found in the cache.", 10 + db "There is no guarantee to the servers identity !",10, 0 + +str23 db "The host key provided by the host does not match the cached one.", 10 + db "This may indicate that the remote server has been compromised!", 10, 0 + +str24 db 10, "If you trust this host, press A to accept and store the (new) key.", 10 + db "Press C to connect to the host but don't store the (new) key.", 10 + db "Press X to abort.", 10, 0 + ssh_ident_ha: - dd_n (ssh_ident.length-2) -ssh_ident: - db "SSH-2.0-KolibriOS_SSH_0.04",13,10 - .length = $ - ssh_ident + dd_n (ssh_msg_ident.length-2) +ssh_msg_ident: + db "SSH-2.0-KolibriOS_SSH_0.05",13,10 + .length = $ - ssh_msg_ident -ssh_kex: + +ssh_msg_kex: db SSH_MSG_KEXINIT .cookie: rd 4 .kex_algorithms: - dd_n .server_host_key_algorithms - .kex_algorithms - 4 - db "diffie-hellman-group-exchange-sha256" ; diffie-hellman-group-exchange-sha1 + str "diffie-hellman-group-exchange-sha256" ; diffie-hellman-group-exchange-sha1 .server_host_key_algorithms: - dd_n .encryption_algorithms_client_to_server - .server_host_key_algorithms - 4 - db "ssh-rsa" ;,ssh-dss + str "ssh-rsa" ;,ssh-dss .encryption_algorithms_client_to_server: - dd_n .encryption_algorithms_server_to_client - .encryption_algorithms_client_to_server - 4 - db "aes256-ctr" ;,aes256-cbc,aes256-cbc,rijndael-cbc@lysator.liu.se,aes192-ctr,aes192-cbc,aes128-ctr,aes128-cbc,blowfish-ctr,blowfish-cbc,3des-ctr,3des-cbc,arcfour256,arcfour128" + str "aes256-ctr" ;,aes256-cbc,aes256-cbc,rijndael-cbc@lysator.liu.se,aes192-ctr,aes192-cbc,aes128-ctr,aes128-cbc,blowfish-ctr,blowfish-cbc,3des-ctr,3des-cbc,arcfour256,arcfour128" .encryption_algorithms_server_to_client: - dd_n .mac_algorithms_client_to_server - .encryption_algorithms_server_to_client - 4 - db "aes256-ctr" ;,aes256-cbc,aes256-cbc,rijndael-cbc@lysator.liu.se,aes192-ctr,aes192-cbc,aes128-ctr,aes128-cbc,blowfish-ctr,blowfish-cbc,3des-ctr,3des-cbc,arcfour256,arcfour128" + str "aes256-ctr" ;,aes256-cbc,aes256-cbc,rijndael-cbc@lysator.liu.se,aes192-ctr,aes192-cbc,aes128-ctr,aes128-cbc,blowfish-ctr,blowfish-cbc,3des-ctr,3des-cbc,arcfour256,arcfour128" .mac_algorithms_client_to_server: - dd_n .mac_algorithms_server_to_client - .mac_algorithms_client_to_server - 4 - db "hmac-sha2-256" ;,hmac-sha1,hmac-sha1-96,hmac-md5" + str "hmac-sha2-256" ;,hmac-sha1,hmac-sha1-96,hmac-md5" .mac_algorithms_server_to_client: - dd_n .compression_algorithms_client_to_server - .mac_algorithms_server_to_client - 4 - db "hmac-sha2-256" ;,hmac-sha1,hmac-sha1-96,hmac-md5" + str "hmac-sha2-256" ;,hmac-sha1,hmac-sha1-96,hmac-md5" .compression_algorithms_client_to_server: - dd_n .compression_algorithms_server_to_client - .compression_algorithms_client_to_server - 4 - db "none" ;,zlib" + str "none" ;,zlib" .compression_algorithms_server_to_client: - dd_n .languages_client_to_server - .compression_algorithms_server_to_client - 4 - db "none" ;,zlib" + str "none" ;,zlib" .languages_client_to_server: - dd_n .languages_server_to_client - .languages_client_to_server - 4 - db "" + str "" .languages_server_to_client: - dd_n .first_kex_packet_follows - .languages_server_to_client - 4 - db "" + str "" .first_kex_packet_follows: db 0 .reserved: dd_n 0 - .length = $ - ssh_kex + .length = $ - ssh_msg_kex -ssh_gex_req: +ssh_msg_gex_req: db SSH_MSG_KEX_DH_GEX_REQUEST dd_n 4096/4 ; DH GEX min dd_n 4096/2 ; DH GEX number of bits dd_n 4096 ; DH GEX Max - .length = $ - ssh_gex_req + .length = $ - ssh_msg_gex_req -ssh_new_keys: +ssh_msg_new_keys: db SSH_MSG_NEWKEYS - .length = $ - ssh_new_keys + .length = $ - ssh_msg_new_keys -ssh_request_service: +ssh_msg_request_service: db SSH_MSG_SERVICE_REQUEST - dd_n 12 ; String length - db "ssh-userauth" ; Service name - .length = $ - ssh_request_service + str "ssh-userauth" ; Service name + .length = $ - ssh_msg_request_service -ssh_request_userauth: - db SSH_MSG_USERAUTH_REQUEST - dd_n 9 - db "user123" ; user name in ISO-10646 UTF-8 encoding [RFC3629] - dd_n 14 - db "ssh-connection" ; service name in US-ASCII - dd_n 8 - db "password" ; method name in US-ASCII: none, publickey, password, hostbased - db 0 ; bool: false - dd_n 14 - db "pass123" - .length = $ - ssh_request_userauth - - -ssh_channel_open: +ssh_msg_channel_open: db SSH_MSG_CHANNEL_OPEN - dd_n 7 - db "session" + str "session" dd_n 0 ; Sender channel - dd_n 1024 ; Initial window size - dd_n 1024 ; maximum packet size - .length = $ - ssh_channel_open + dd_n BUFFERSIZE ; Initial window size + dd_n PACKETSIZE ; maximum packet size + .length = $ - ssh_msg_channel_open -ssh_channel_request: + +ssh_msg_channel_close: + db SSH_MSG_CHANNEL_CLOSE + dd_n 0 ; Sender channel + .length = $ - ssh_msg_channel_close + + +ssh_msg_channel_request: db SSH_MSG_CHANNEL_REQUEST dd_n 0 ; Recipient channel - dd_n 7 - db "pty-req" + str "pty-req" db 1 ; Bool: want reply - dd_n 5 - db "xterm" + str "xterm" dd_n 80 ; terminal width (rows) dd_n 25 ; terminal height (rows) - dd_n 0 ; terminal width (pixels) - dd_n 0 ; terminal height (pixels) + dd_n 80*8 ; terminal width (pixels) + dd_n 25*16 ; terminal height (pixels) dd_n 0 ; list of supported opcodes - .length = $ - ssh_channel_request + .length = $ - ssh_msg_channel_request -ssh_shell_request: + +ssh_msg_shell_request: db SSH_MSG_CHANNEL_REQUEST dd_n 0 ; Recipient channel - dd_n 5 - db "shell" + str "shell" db 1 ; Bool: want reply - .length = $ - ssh_shell_request + .length = $ - ssh_msg_shell_request -ssh_channel_data: + +ssh_msg_channel_data: db SSH_MSG_CHANNEL_DATA dd_n 0 ; Sender channel - dd_n 1 - db ? - .length = $ - ssh_channel_data + .len dd ? + .data rb MAX_INPUT_LENGTH + 1 + + +ssh_msg_channel_window_adjust: + db SSH_MSG_CHANNEL_WINDOW_ADJUST + dd_n 0 ; Sender channel + .wnd dd ? + .length = $ - ssh_msg_channel_window_adjust include_debug_strings @@ -931,10 +607,9 @@ import console, \ con_gets, 'con_gets', \ con_cls, 'con_cls', \ con_getch2, 'con_getch2', \ - con_set_cursor_pos, 'con_set_cursor_pos', \ - con_write_string, 'con_write_string', \ - con_get_flags, 'con_get_flags', \ - con_set_flags, 'con_set_flags' + con_get_flags, 'con_get_flags', \ + con_set_title, 'con_set_title', \ + con_get_input, 'con_get_input' import libcrash, \ sha256_init, 'sha256_init', \ @@ -953,8 +628,9 @@ i_end: IncludeUGlobals -params rb 1024 +params rb MAX_HOSTNAME_LENGTH -con ssh_connection +ssh_con sshlib_connection +ssh_chan sshlib_channel mem: diff --git a/programs/network/ssh/ssh_transport.inc b/programs/network/ssh/ssh_transport.inc deleted file mode 100644 index 0fc978c660..0000000000 --- a/programs/network/ssh/ssh_transport.inc +++ /dev/null @@ -1,274 +0,0 @@ -; ssh_transport.inc - SSH transport layer -; -; Copyright (C) 2016-2021 Jeffrey Amelynck -; -; This program is free software: you can redistribute it and/or modify -; it under the terms of the GNU General Public License as published by -; the Free Software Foundation, either version 3 of the License, or -; (at your option) any later version. -; -; This program is distributed in the hope that it will be useful, -; but WITHOUT ANY WARRANTY; without even the implied warranty of -; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -; GNU General Public License for more details. -; -; You should have received a copy of the GNU General Public License -; along with this program. If not, see . - - -struct ssh_packet_header - packet_length dd ? ; The length of the packet in bytes, not including 'mac' or the - ; 'packet_length' field itself. - padding_length db ? ; Length of 'random padding' (bytes). - - message_code db ? ; First byte of payload -ends - -proc padding_zero - - xor eax, eax - ret - -endp - -proc ssh_recv_packet connection, flags - -locals - data_length dd ? ; Total length of packet without MAC - socket_error dd ? -endl - - DEBUGF 2, "> " -; Receive first block (Read length, padding length, message code) - mov ebx, [connection] - mov ecx, [ebx+ssh_connection.socketnum] - mov esi, [ebx+ssh_connection.rx_crypt_blocksize] - lea edx, [ebx+ssh_connection.rx_buffer] - mov edi, [flags] - mcall recv - mov [socket_error], ebx - DEBUGF 1, "chunk = %u ", eax - mov ebx, [connection] - cmp eax, [ebx+ssh_connection.rx_crypt_blocksize] - jne .fail - -; Decrypt first block - cmp [ebx+ssh_connection.rx_crypt_proc], 0 - je @f - pusha - lea esi, [ebx+ssh_connection.rx_buffer] - stdcall [ebx+ssh_connection.rx_crypt_proc], [ebx+ssh_connection.rx_crypt_ctx_ptr], esi, esi - popa - @@: - -; Check data length - mov esi, [ebx+ssh_connection.rx_buffer.packet_length] - bswap esi ; convert length to little endian - mov [ebx+ssh_connection.rx_buffer.packet_length], esi - DEBUGF 1, "packet length=%u ", esi - cmp esi, BUFFERSIZE - ja .fail ; packet is too large - -; Calculate amount of remaining data - add esi, 4 ; Packet length field itself is not included in the count - sub esi, [ebx+ssh_connection.rx_crypt_blocksize] ; Already received this amount of data - add esi, [ebx+ssh_connection.rx_mac_length] - jz .got_all_data - -; Receive remaining data - lea edx, [ebx+ssh_connection.rx_buffer] - add edx, [ebx+ssh_connection.rx_crypt_blocksize] - mov ecx, [ebx+ssh_connection.socketnum] - mov edi, [flags] - .receive_loop: - mcall recv - DEBUGF 1, "chunk = %u ", eax - cmp eax, 0 - jbe .fail - add edx, eax - sub esi, eax - jnz .receive_loop - -; Decrypt data - mov ebx, [connection] - cmp [ebx+ssh_connection.rx_crypt_proc], 0 - je .decrypt_complete - mov ecx, [ebx+ssh_connection.rx_buffer.packet_length] - add ecx, 4 ; Packet_length field itself - sub ecx, [ebx+ssh_connection.rx_crypt_blocksize] ; Already decrypted this amount of data - jz .decrypt_complete - - lea esi, [ebx+ssh_connection.rx_buffer] - add esi, [ebx+ssh_connection.rx_crypt_blocksize] - .decrypt_loop: - pusha - stdcall [ebx+ssh_connection.rx_crypt_proc], [ebx+ssh_connection.rx_crypt_ctx_ptr], esi, esi - popa - add esi, [ebx+ssh_connection.rx_crypt_blocksize] - sub ecx, [ebx+ssh_connection.rx_crypt_blocksize] - jnz .decrypt_loop - .decrypt_complete: - -; Authenticate message - cmp [ebx+ssh_connection.rx_mac_proc], 0 - je .mac_complete - lea esi, [ebx+ssh_connection.rx_seq] - mov ecx, [ebx+ssh_connection.rx_buffer.packet_length] - add ecx, 8 ; packet_length field itself + sequence number - lea eax, [ebx+ssh_connection.rx_mac_ctx] - mov edx, [ebx+ssh_connection.rx_buffer.packet_length] - bswap edx ; convert length to big endian - mov [ebx+ssh_connection.rx_buffer.packet_length], edx - stdcall [ebx+ssh_connection.rx_mac_proc], eax, esi, ecx - mov edx, [ebx+ssh_connection.rx_buffer.packet_length] - bswap edx ; convert length to little endian - mov [ebx+ssh_connection.rx_buffer.packet_length], edx - - lea esi, [ebx+ssh_connection.rx_mac_ctx] - lea edi, [ebx+ssh_connection.rx_buffer] - add edi, [ebx+ssh_connection.rx_buffer.packet_length] - add edi, 4 - mov ecx, [ebx+ssh_connection.rx_mac_length] - shr ecx, 2 - repe cmpsd - jne .mac_failed - .mac_complete: - add byte[ebx+ssh_connection.rx_seq+3], 1 ; Update sequence counter - adc byte[ebx+ssh_connection.rx_seq+2], 0 - adc byte[ebx+ssh_connection.rx_seq+1], 0 - adc byte[ebx+ssh_connection.rx_seq+0], 0 - -; Return useful data length to the caller via eax register - .got_all_data: - mov eax, [ebx+ssh_connection.rx_buffer.packet_length] - movzx ebx, [ebx+ssh_connection.rx_buffer.padding_length] - sub eax, ebx - DEBUGF 1, "useful data length=%u\n", eax - ret - - .fail: - DEBUGF 3, "ssh_recv_packet failed!\n" - mov eax, -1 - mov ebx, [socket_error] - ret - - .mac_failed: - DEBUGF 3, "ssh_recv_packet MAC failed!\n" - mov eax, -2 - mov ebx, [socket_error] - ret - -endp - - -proc ssh_send_packet connection, buf, payload_size, flags - -locals - packet_size dd ? -endl - DEBUGF 2, "< " - -; Check how many bytes we should pad - mov eax, [payload_size] - inc eax ; padding length byte - lea edx, [eax+4] ; total packet size (without padding and MAC) - mov [packet_size], edx - - mov ecx, [connection] - mov ebx, [ecx+ssh_connection.tx_pad_size] - dec ebx - and edx, ebx - neg edx - add edx, [ecx+ssh_connection.tx_pad_size] - add edx, [ecx+ssh_connection.tx_pad_size] - DEBUGF 1, "padding %u bytes ", edx - add [packet_size], edx ; total packet size with padding - -; Start building the packet -; First comes the packet length, in network byte order ofcourse. - add eax, edx - DEBUGF 1, "total size: %u ", eax - bswap eax - lea edi, [ecx+ssh_connection.tx_buffer] - stosd -; Then the padding length - mov al, dl - stosb -; And the actual payload bytes - mov esi, [buf] - mov ecx, [payload_size] - rep movsb - -; Append the packet with #edx padding bytes. -; Since we must pad at least 8 bytes, we can always use DWORD writes. -; First do an (unaligned) write exactly following the data - dec edx - mov esi, edx - shr esi, 2 ; number dwords - mov ebx, edx - and ebx, 3 - inc ebx ; number bytes in first write (1-4) - mov edx, [connection] - call [edx+ssh_connection.tx_pad_proc] - mov dword[edi], eax - add edi, ebx -; Then, do as many aligned writes as nescessary - mov ebx, [connection] - @@: - call [ebx+ssh_connection.tx_pad_proc] - stosd - dec esi - jnz @r - -; Append the packet with Message Authentication Code - mov edx, [connection] - cmp [edx+ssh_connection.tx_mac_proc], 0 - je .mac_complete - DEBUGF 1, "MAC sequence number: 0x%x\n", [edx+ssh_connection.tx_seq] - lea esi, [edx+ssh_connection.tx_seq] - mov ecx, [packet_size] - add ecx, 4 ; Sequence number length - lea eax, [edx+ssh_connection.tx_mac_ctx] - stdcall [edx+ssh_connection.tx_mac_proc], eax, esi, ecx - - lea esi, [edx+ssh_connection.tx_mac_ctx] - lea edi, [edx+ssh_connection.tx_buffer] - add edi, [packet_size] - mov ecx, [edx+ssh_connection.tx_mac_length] - shr ecx, 2 - rep movsd - .mac_complete: - add byte[edx+ssh_connection.tx_seq+3], 1 ; Update sequence counter - adc byte[edx+ssh_connection.tx_seq+2], 0 - adc byte[edx+ssh_connection.tx_seq+1], 0 - adc byte[edx+ssh_connection.tx_seq+0], 0 - -; Now, encrypt everything but MAC - cmp [edx+ssh_connection.tx_crypt_proc], 0 - je .encrypt_complete - lea esi, [edx+ssh_connection.tx_buffer] - mov ecx, [packet_size] - .encrypt_loop: - pusha - stdcall [edx+ssh_connection.tx_crypt_proc], [edx+ssh_connection.tx_crypt_ctx_ptr], esi, esi - popa - add esi, [edx+ssh_connection.tx_crypt_blocksize] - sub ecx, [edx+ssh_connection.tx_crypt_blocksize] - jnz .encrypt_loop - .encrypt_complete: - -; Send the packet - mov ebx, [connection] - mov ecx, [ebx+ssh_connection.socketnum] - lea edx, [ebx+ssh_connection.tx_buffer] - mov esi, [packet_size] - add esi, [ebx+ssh_connection.tx_mac_length] - mov edi, [flags] - mcall send - - DEBUGF 1, "\n" - - ret - -endp - diff --git a/programs/network/ssh/sshlib.inc b/programs/network/ssh/sshlib.inc new file mode 100644 index 0000000000..10050415d0 --- /dev/null +++ b/programs/network/ssh/sshlib.inc @@ -0,0 +1,155 @@ +; sshlib.inc - SSHlib constants +; +; Copyright (C) 2016-2021 Jeffrey Amelynck +; +; This program is free software: you can redistribute it and/or modify +; it under the terms of the GNU General Public License as published by +; the Free Software Foundation, either version 3 of the License, or +; (at your option) any later version. +; +; This program is distributed in the hope that it will be useful, +; but WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +; GNU General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program. If not, see . + + +; Error codes + +SSHLIB_ERR_NOMEM = -1 +SSHLIB_ERR_SOCKET = -2 +SSHLIB_ERR_PROTOCOL = -3 +SSHLIB_ERR_HOSTNAME = -4 +SSHLIB_ERR_DISCONNECTING = -5 +SSHLIB_ERR_MAC_VERIFY_FAIL = -6 +SSHLIB_ERR_HKEY_NO_ALGO = -7 +SSHLIB_ERR_HKEY_VERIFY_FAIL = -8 +SSHLIB_ERR_HKEY_SIGNATURE = -9 +SSHLIB_ERR_HKEY_PUBLIC_KEY = -10 + +; Channel status codes + +SSHLIB_CHAN_STAT_CONNECTING = 0 +SSHLIB_CHAN_STAT_CONNECTED = 1 +SSHLIB_CHAN_STAT_EOF_RECEIVED = 2 +SSHLIB_CHAN_STAT_CLOSING = 3 +SSHLIB_CHAN_STAT_CLOSED = 3 + +; Connection status codes + +SSHLIB_CON_STAT_INIT = 0 +SSHLIB_CON_STAT_KEX_DONE = 1 + +; Algorithm identifier codes + +SSHLIB_ALGO_NONE = 0 + +SSHLIB_KEX_DH_SHA1 = 1 +SSHLIB_KEX_DH_SHA256 = 2 + +SSHLIB_HOSTKEY_DSS = 1 +SSHLIB_HOSTKEY_RSA = 2 +SSHLIB_HOSTKEY_RSA_SHA2_256 = 3 +SSHLIB_HOSTKEY_RSA_SHA2_512 = 4 + +SSHLIB_CRYPT_BLOWFISH_CTR = 1 +SSHLIB_CRYPT_BLOWFISH_CBC = 2 +SSHLIB_CRYPT_AES128_CTR = 3 +SSHLIB_CRYPT_AES128_CBC = 4 +SSHLIB_CRYPT_AES192_CTR = 5 +SSHLIB_CRYPT_AES192_CBC = 6 +SSHLIB_CRYPT_AES256_CTR = 7 +SSHLIB_CRYPT_AES256_CBC = 8 + +SSHLIB_HMAC_MD5 = 1 +SSHLIB_HMAC_SHA1 = 2 +SSHLIB_HMAC_SHA1_96 = 3 +SSHLIB_HMAC_SHA2_256 = 4 + +SSHLIB_COMPR_NONE = 1 +SSHLIB_COMPR_ZLIB = 2 + +; Hostkey + +SSHLIB_HOSTKEY_PROBLEM_UNKNOWN = 0 +SSHLIB_HOSTKEY_PROBLEM_MISMATCH = 1 + +SSHLIB_HOSTKEY_REFUSE = -1 +SSHLIB_HOSTKEY_ACCEPT = 0 +SSHLIB_HOSTKEY_ONCE = 1 + +; SSH network packet header + +struct ssh_packet_header + + packet_length dd ? ; The length of the packet in bytes, not including 'mac' or the + ; 'packet_length' field itself. + padding_length db ? ; Length of 'random padding' (bytes). + + message_code db ? ; First byte of payload + +ends + +; SSH connection structure + +struct sshlib_connection + + status dd ? + + socketnum dd ? + + rx_crypt_proc dd ? + tx_crypt_proc dd ? + rx_crypt_ctx_ptr dd ? + tx_crypt_ctx_ptr dd ? + rx_crypt_blocksize dd ? + tx_crypt_blocksize dd ? + + tx_pad_size dd ? ; = Max(8, tx_crypt_blocksize) + tx_pad_proc dd ? + + rx_mac_proc dd ? + tx_mac_proc dd ? + rx_mac_ctx hmac_sha256_context + tx_mac_ctx hmac_sha256_context + rx_mac_length dd ? + tx_mac_length dd ? + + rx_mac_seqnr dd ? ; DO NOT MOVE + rx_buffer ssh_packet_header + rb BUFFERSIZE-sizeof.ssh_packet_header + + tx_mac_seqnr dd ? ; DO NOT MOVE + tx_buffer ssh_packet_header + rb PACKETSIZE-sizeof.ssh_packet_header + + part_ex_hash_ctx crash_ctx + session_id rb SHA256_HASH_SIZE + + algo_kex dd ? + algo_hostkey dd ? + algo_crypt_rx dd ? + algo_crypt_tx dd ? + algo_mac_rx dd ? + algo_mac_tx dd ? + algo_compr_rx dd ? + algo_compr_tx dd ? + + hostname_sz rb MAX_HOSTNAME_LENGTH + +ends + +; SSH channel structure + +struct sshlib_channel + + id dd ? ; Channel ID (big endian) + status dd ? ; Channel status + rcv_wnd dd ? ; Receive window + snd_wnd dd ? ; Send window + +; rcv_callb dd ? ; TODO + +ends diff --git a/programs/network/ssh/sshlib_channel.inc b/programs/network/ssh/sshlib_channel.inc new file mode 100644 index 0000000000..2265ce940d --- /dev/null +++ b/programs/network/ssh/sshlib_channel.inc @@ -0,0 +1,87 @@ +; sshlib_channel.inc - SSH channel +; +; Copyright (C) 2016-2021 Jeffrey Amelynck +; +; This program is free software: you can redistribute it and/or modify +; it under the terms of the GNU General Public License as published by +; the Free Software Foundation, either version 3 of the License, or +; (at your option) any later version. +; +; This program is distributed in the hope that it will be useful, +; but WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +; GNU General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program. If not, see . + + +proc sshlib_chan_open con_ptr; Channel struct ptr?! + +; >> Open channel + + DEBUGF 2, "SSH: Open channel\n" + + mov [ssh_chan.rcv_wnd], BUFFERSIZE + mov [ssh_chan.snd_wnd], 0 + stdcall sshlib_send_packet, [con_ptr], ssh_msg_channel_open, ssh_msg_channel_open.length, 0 + cmp eax, 0 + jl .err + +; << Check for channel open confirmation + + stdcall sshlib_msg_handler, [con_ptr], 0 + cmp eax, 0 + jl .err + + mov esi, [con_ptr] + cmp [esi + sshlib_connection.rx_buffer.message_code], SSH_MSG_CHANNEL_OPEN_CONFIRMATION + jne .err_proto + +; >> Channel request: pty + + DEBUGF 2, "SSH: Request pty\n" + + stdcall sshlib_send_packet, [con_ptr], ssh_msg_channel_request, ssh_msg_channel_request.length, 0 + cmp eax, 0 + jl .err + +; << Check for channel request confirmation + + stdcall sshlib_msg_handler, [con_ptr], 0 + cmp eax, 0 + jl .err + + mov esi, [con_ptr] + cmp [esi + sshlib_connection.rx_buffer.message_code], SSH_MSG_CHANNEL_SUCCESS + jne .err_proto + +; >> Channel request: shell + + DEBUGF 2, "SSH: Request shell\n" + + stdcall sshlib_send_packet, [con_ptr], ssh_msg_shell_request, ssh_msg_shell_request.length, 0 + cmp eax, 0 + jl .err + +; << Check for channel request confirmation + +; TODO: timeout + .wait_success: + stdcall sshlib_msg_handler, [con_ptr], 0 + cmp eax, 0 + jl .err + + mov esi, [con_ptr] + cmp [esi + sshlib_connection.rx_buffer.message_code], SSH_MSG_CHANNEL_SUCCESS + jne .wait_success + + xor eax, eax + .err: + ret + + .err_proto: + mov eax, SSHLIB_ERR_PROTOCOL + ret + +endp \ No newline at end of file diff --git a/programs/network/ssh/sshlib_connection.inc b/programs/network/ssh/sshlib_connection.inc new file mode 100644 index 0000000000..44fe963f56 --- /dev/null +++ b/programs/network/ssh/sshlib_connection.inc @@ -0,0 +1,396 @@ +; sshlib_connection.inc - SSH connection +; +; Copyright (C) 2016-2021 Jeffrey Amelynck +; +; This program is free software: you can redistribute it and/or modify +; it under the terms of the GNU General Public License as published by +; the Free Software Foundation, either version 3 of the License, or +; (at your option) any later version. +; +; This program is distributed in the hope that it will be useful, +; but WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +; GNU General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program. If not, see . + +proc sshlib_connect con_ptr, hostname_sz + +locals + socketnum dd ? + sockaddr sockaddr_in + ctx_ptr dd ? +endl + + mov edi, [con_ptr] + lea eax, [edi + sshlib_connection.part_ex_hash_ctx] + mov [ctx_ptr], eax + +; Set default values in sockaddr struct + mov [sockaddr.sin_family], AF_INET4 + mov [sockaddr.sin_port], 22 shl 8 + +; Parse hostname_sz +; Verify length, extract port number if given and copy base url to sshlib_connection struct +; Port number, if provided, will be written in sockaddr struct. +; Hostname ends with any character equal to 0x20 or lower + + mov esi, [hostname_sz] + lea edi, [edi + sshlib_connection.hostname_sz] + mov ecx, MAX_HOSTNAME_LENGTH + @@: + dec ecx + jz .err_hostname + lodsb + cmp al, ':' + je .do_port + stosb + cmp al, 0x20 + ja @r + mov byte[edi-1], 0 + jmp .hostname_ok + + .do_port: + xor eax, eax + xor ebx, ebx + mov byte[edi-1], 0 + .portloop: + lodsb + cmp al, 0x20 + jbe .port_done + sub al, '0' + jb .err_hostname + cmp al, 9 + ja .err_hostname + lea ebx, [ebx*4+ebx] + shl ebx, 1 + add ebx, eax + jmp .portloop + .port_done: + xchg bl, bh + mov [sockaddr.sin_port], bx + + .hostname_ok: +; resolve name + push esp ; reserve stack place + push esp + mov eax, [con_ptr] + lea eax, [eax+sshlib_connection.hostname_sz] + invoke getaddrinfo, eax, 0, 0 + pop esi +; test for error + test eax, eax + jnz .err_hostname + +; convert IP address to decimal notation + mov eax, [esi+addrinfo.ai_addr] + mov eax, [eax+sockaddr_in.sin_addr] + mov [sockaddr.sin_addr], eax + invoke inet_ntoa, eax +; write result + stdcall sshlib_callback_connecting, [con_ptr], eax +; free allocated memory + invoke freeaddrinfo, esi + +; Create socket + mcall socket, AF_INET4, SOCK_STREAM, 0 + cmp eax, -1 + jz .err_sock + mov [socketnum], eax + mov ebx, [con_ptr] + mov [ebx + sshlib_connection.socketnum], eax + +; Connect + DEBUGF 2, "Connecting to server\n" + lea edx, [sockaddr] + mcall connect, [socketnum], , sizeof.sockaddr_in + test eax, eax + jnz .err_sock + +; Start calculating hash + invoke sha256_init, [ctx_ptr] +; HASH: string V_C, the client's version string (CR and NL excluded) + invoke sha256_update, [ctx_ptr], ssh_ident_ha, ssh_msg_ident.length+4-2 + +; >> Send our identification string + DEBUGF 2, "Sending ID string\n" + mcall send, [socketnum], ssh_msg_ident, ssh_msg_ident.length, 0 + cmp eax, -1 + je .err_sock + +; << Check protocol version of server + mov edx, [con_ptr] + lea edx, [edx + sshlib_connection.rx_buffer + 4] + mcall recv, [socketnum], , PACKETSIZE, 0 + cmp eax, -1 + je .err_sock + + DEBUGF 2, "Received ID string\n" + cmp dword[edx], "SSH-" + jne .err_proto + cmp dword[edx+4], "2.0-" + jne .err_proto + +; HASH: string V_S, the server's version string (CR and NL excluded) + lea ecx, [eax+2] + sub eax, 2 + bswap eax + sub edx, 4 + mov dword[edx], eax + invoke sha256_update, [ctx_ptr], edx, ecx + +; >> Key Exchange init + mov eax, [con_ptr] + mov [eax + sshlib_connection.status], SSHLIB_CON_STAT_INIT + + mov [eax + sshlib_connection.algo_kex], SSHLIB_ALGO_NONE + mov [eax + sshlib_connection.algo_hostkey], SSHLIB_ALGO_NONE + mov [eax + sshlib_connection.algo_crypt_rx], SSHLIB_ALGO_NONE + mov [eax + sshlib_connection.algo_crypt_tx], SSHLIB_ALGO_NONE + mov [eax + sshlib_connection.algo_mac_rx], SSHLIB_ALGO_NONE + mov [eax + sshlib_connection.algo_mac_tx], SSHLIB_ALGO_NONE + mov [eax + sshlib_connection.algo_compr_rx], SSHLIB_ALGO_NONE + mov [eax + sshlib_connection.algo_compr_tx], SSHLIB_ALGO_NONE + + mov [eax + sshlib_connection.rx_mac_seqnr], 0 + mov [eax + sshlib_connection.tx_mac_seqnr], 0 + mov [eax + sshlib_connection.rx_crypt_blocksize], 4 ; minimum blocksize + mov [eax + sshlib_connection.tx_crypt_blocksize], 4 + mov [eax + sshlib_connection.rx_crypt_proc], sshlib_crypt_null + mov [eax + sshlib_connection.tx_crypt_proc], sshlib_crypt_null + mov [eax + sshlib_connection.rx_mac_proc], 0 + mov [eax + sshlib_connection.tx_mac_proc], 0 + mov [eax + sshlib_connection.rx_mac_length], 0 + mov [eax + sshlib_connection.tx_mac_length], 0 + mov [eax + sshlib_connection.tx_pad_size], 8 + mov [eax + sshlib_connection.tx_pad_proc], sshlib_padd_null + + DEBUGF 2, "Sending KEX init\n" + mov edi, ssh_msg_kex.cookie + call MBRandom + stosd + call MBRandom + stosd + call MBRandom + stosd + call MBRandom + stosd + stdcall sshlib_send_packet, [con_ptr], ssh_msg_kex, ssh_msg_kex.length, 0 + cmp eax, -1 + je .err_sock + +; HASH: string I_C, the payload of the client's SSH_MSG_KEXINIT + mov esi, [con_ptr] + mov eax, [esi+sshlib_connection.tx_buffer.packet_length] + bswap eax + movzx ebx, [esi+sshlib_connection.tx_buffer.padding_length] + sub eax, ebx + dec eax + lea edx, [eax+4] + bswap eax + lea esi, [esi+sshlib_connection.tx_buffer+1] + mov dword[esi], eax + invoke sha256_update, [ctx_ptr], esi, edx + +; << Check key exchange init of server + stdcall sshlib_recv_packet, [con_ptr], 0 + cmp eax, -1 + je .err_sock + + mov esi, [con_ptr] + cmp [esi + sshlib_connection.rx_buffer.message_code], SSH_MSG_KEXINIT + jne .err_proto + DEBUGF 2, "Received KEX init\n" + + lea esi, [esi + sshlib_connection.rx_buffer + sizeof.ssh_packet_header + 16] + lodsd + bswap eax + DEBUGF 1, "kex_algorithms: %s\n", esi + add esi, eax + lodsd + bswap eax + DEBUGF 1, "server_host_key_algorithms: %s\n", esi + add esi, eax + lodsd + bswap eax + DEBUGF 1, "encryption_algorithms_client_to_server: %s\n", esi + add esi, eax + lodsd + bswap eax + DEBUGF 1, "encryption_algorithms_server_to_client: %s\n", esi + add esi, eax + lodsd + bswap eax + DEBUGF 1, "mac_algorithms_client_to_server: %s\n", esi + add esi, eax + lodsd + bswap eax + DEBUGF 1, "mac_algorithms_server_to_client: %s\n", esi + add esi, eax + lodsd + bswap eax + DEBUGF 1, "compression_algorithms_client_to_server: %s\n", esi + add esi, eax + lodsd + bswap eax + DEBUGF 1, "compression_algorithms_server_to_client: %s\n", esi + add esi, eax + lodsd + bswap eax + DEBUGF 1, "languages_client_to_server: %s\n", esi + add esi, eax + lodsd + bswap eax + DEBUGF 1, "languages_server_to_client: %s\n", esi + add esi, eax + lodsb + DEBUGF 1, "KEX First Packet Follows: %u\n", al + +; TODO: parse this structure and set algorithm codes accordingly +; FIXME: hardcoded for now + mov esi, [con_ptr] + mov [esi+sshlib_connection.algo_kex], SSHLIB_KEX_DH_SHA256 + mov [esi+sshlib_connection.algo_hostkey], SSHLIB_HOSTKEY_RSA + mov [esi+sshlib_connection.algo_crypt_rx], SSHLIB_CRYPT_AES256_CTR + mov [esi+sshlib_connection.algo_crypt_tx], SSHLIB_CRYPT_AES256_CTR + mov [esi+sshlib_connection.algo_mac_rx], SSHLIB_HMAC_SHA2_256 + mov [esi+sshlib_connection.algo_mac_tx], SSHLIB_HMAC_SHA2_256 + mov [esi+sshlib_connection.algo_compr_rx], SSHLIB_COMPR_NONE + mov [esi+sshlib_connection.algo_compr_tx], SSHLIB_COMPR_NONE + +; HASH: string I_S, the payload of the servers's SSH_MSG_KEXINIT + mov esi, [con_ptr] + mov eax, [esi+sshlib_connection.rx_buffer.packet_length] + movzx ebx, [esi+sshlib_connection.rx_buffer.padding_length] + sub eax, ebx + dec eax + lea edx, [eax+4] + bswap eax + lea esi, [esi+sshlib_connection.rx_buffer+1] + mov dword[esi], eax + invoke sha256_update, [ctx_ptr], esi, edx + +; Exchange keys with the server + + stdcall sshlib_dh_gex, [con_ptr] + test eax, eax + jnz .err + +; Re-seed RNG for padding bytes + + call create_seed + call init_random + + xor eax, eax + ret + + .err_hostname: + mov eax, SSHLIB_ERR_HOSTNAME + ret + + .err_sock: + mov eax, SSHLIB_ERR_SOCKET + ret + + .err_proto: + mov eax, SSHLIB_ERR_PROTOCOL + ret + + .err: + ret + +endp + + + + +; Handle common messages and return to caller for specific ones +proc sshlib_msg_handler, con_ptr, flags + + .recv: +; Send a window update if advertised window drops below half + cmp [ssh_chan.rcv_wnd], BUFFERSIZE/2 + ja .no_wnd + mov eax, BUFFERSIZE + bswap eax + mov [ssh_msg_channel_window_adjust.wnd], eax + stdcall sshlib_send_packet, [con_ptr], ssh_msg_channel_window_adjust, ssh_msg_channel_window_adjust.length, 0 + mov [ssh_chan.rcv_wnd], BUFFERSIZE + .no_wnd: + +; Receive 1 SSH packet + stdcall sshlib_recv_packet, [con_ptr], [flags] + cmp eax, 0 + jle .ret + + mov esi, [con_ptr] + lea esi, [esi + sshlib_connection.rx_buffer] + mov al, [esi + ssh_packet_header.message_code] + inc esi + + cmp al, SSH_MSG_DISCONNECT + je .disc + cmp al, SSH_MSG_IGNORE + je .ign + cmp al, SSH_MSG_DEBUG + je .dbg + cmp al, SSH_MSG_GLOBAL_REQUEST + je .glob_req + cmp al, SSH_MSG_CHANNEL_WINDOW_ADJUST + je .chan_win_adj +; cmp al, SSH_MSG_CHANNEL_REQUEST +; je .chan_req + cmp al, SSH_MSG_CHANNEL_EOF + je .chan_eof + cmp al, SSH_MSG_CHANNEL_CLOSE + je .chan_close + + .ret: + ret + + .disc: + DEBUGF 3, "SSH: Disconnect message received\n" + mov eax, SSHLIB_ERR_DISCONNECTING + ret + + .ign: + DEBUGF 3, "SSH: Ignore MSG received\n" + jmp .recv + + .dbg: + DEBUGF 3, "SSH: Debug MSG received\n" + ;TODO + jmp .recv + + .glob_req: + DEBUGF 3, "SSH: Global MSG received\n" + ;TODO + jmp .recv + + .chan_win_adj: + mov eax, dword[esi] + bswap eax + mov [ssh_chan.snd_wnd], eax + ; TODO: validate channel number, act accordingly + DEBUGF 3, "SSH: Channel %u window update received\n", eax + jmp .recv + + .chan_eof: + mov eax, dword[esi] + bswap eax + ; TODO: validate channel number, act accordingly + DEBUGF 3, "SSH: Channel %u EOF received\n", eax + jmp .recv + + .chan_close: + mov eax, dword[esi] + bswap eax + ; TODO: validate channel number + DEBUGF 3, "SSH: Channel %u close received\n", eax + ; Reply with close message + stdcall sshlib_send_packet, [con_ptr], ssh_msg_channel_close, ssh_msg_channel_close.length, 0 + xor eax, eax + ret + +endp \ No newline at end of file diff --git a/programs/network/ssh/sshlib_dh_gex.inc b/programs/network/ssh/sshlib_dh_gex.inc new file mode 100644 index 0000000000..b4fb2619cf --- /dev/null +++ b/programs/network/ssh/sshlib_dh_gex.inc @@ -0,0 +1,507 @@ +; sshlib_dh_gex.inc - Diffie Hellman Group exchange +; +; Copyright (C) 2015-2021 Jeffrey Amelynck +; +; This program is free software: you can redistribute it and/or modify +; it under the terms of the GNU General Public License as published by +; the Free Software Foundation, either version 3 of the License, or +; (at your option) any later version. +; +; This program is distributed in the hope that it will be useful, +; but WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +; GNU General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program. If not, see . + +; https://www.ietf.org/rfc/rfc4419.txt + +proc sshlib_dh_gex con_ptr + +locals + + mpint_tmp dd ? + + mpint_p dd ? + mpint_g dd ? + mpint_x dd ? + mpint_e dd ? + mpint_f dd ? + mpint_K_big dd ? + + k_h_ctx dd ? + temp_ctx dd ? + + H dd ? ; exchange hash + + rx_iv dd ? ; Rx initialisation vector + tx_iv dd ? ; Tx initialisation vector + rx_enc_key dd ? ; Rx encryption key + tx_enc_key dd ? ; Tx encryption key + rx_int_key dd ? ; Rx integrity key + tx_int_key dd ? ; Tx integrity key + + K_length dd ? + + session_id_x rb SHA256_HASH_SIZE+1 + + str_K_S dd ? ; server public host key and certificates (K_S) + mpint_f_big dd ? ; pointer to original + str_s_of_H dd ? ; signature of H + +endl + +; Allocate memory for temp variables + + mov ecx, 7*(MAX_BITS/8+4) + 7*SHA256_HASH_SIZE + 2*sizeof.crash_ctx + mcall 68, 12 + test eax, eax + jz .err_nomem + +; Init pointers for temp variables + + mov [mpint_tmp], eax + add eax, (MAX_BITS/8+4) + mov [mpint_p], eax + add eax, (MAX_BITS/8+4) + mov [mpint_g], eax + add eax, (MAX_BITS/8+4) + mov [mpint_x], eax + add eax, (MAX_BITS/8+4) + mov [mpint_e], eax + add eax, (MAX_BITS/8+4) + mov [mpint_f], eax + add eax, (MAX_BITS/8+4) + mov [mpint_K_big], eax + add eax, (MAX_BITS/8+4) + + mov [k_h_ctx], eax + add eax, sizeof.crash_ctx + mov [temp_ctx], eax + add eax, sizeof.crash_ctx + + mov [H], eax + add eax, SHA256_HASH_SIZE + mov [rx_iv], eax + add eax, SHA256_HASH_SIZE + mov [tx_iv], eax + add eax, SHA256_HASH_SIZE + mov [rx_enc_key], eax + add eax, SHA256_HASH_SIZE + mov [tx_enc_key], eax + add eax, SHA256_HASH_SIZE + mov [rx_int_key], eax + add eax, SHA256_HASH_SIZE + mov [tx_int_key], eax +; add eax, SHA256_HASH_SIZE + +; Copy the partial exchange hash to our temporary one + + mov esi, [con_ptr] + lea esi, [esi+sshlib_connection.part_ex_hash_ctx] + mov edi, [temp_ctx] + mov ecx, sizeof.crash_ctx/4 + rep movsd + +;---------------------------------------------- +; >> Send Diffie-Hellman Group Exchange Request + + DEBUGF 2, "Sending GEX\n" + stdcall sshlib_send_packet, [con_ptr], ssh_msg_gex_req, ssh_msg_gex_req.length, 0 + cmp eax, 0 + jl .err + +;--------------------------------------------- +; << Parse Diffie-Hellman Group Exchange Group + + stdcall sshlib_recv_packet, [con_ptr], 0 + cmp eax, 0 + jl .err + + mov ebx, [con_ptr] + cmp [ebx + sshlib_connection.rx_buffer.message_code], SSH_MSG_KEX_DH_GEX_GROUP + jne .err_proto + DEBUGF 2, "Received GEX group\n" + + lea esi, [ebx + sshlib_connection.rx_buffer + sizeof.ssh_packet_header] + stdcall mpint_to_little_endian, [mpint_p], esi + add esi, 4 + add esi, eax + DEBUGM 1, "DH modulus (p): ", [mpint_p] + + stdcall mpint_to_little_endian, [mpint_g], esi + add esi, 4 + add esi, eax + DEBUGM 1, "DH base (g): ", [mpint_g] + +;------------------------------------------- +; >> Send Diffie-Hellman Group Exchange Init + +; generate a random number x, where 1 < x < (p-1)/2 + mov edi, [mpint_x] + mov dword[edi], DH_PRIVATE_KEY_SIZE/8 + add edi, 4 + mov ecx, DH_PRIVATE_KEY_SIZE/8/4 + @@: + push ecx + call MBRandom + pop ecx + stosd + dec ecx + jnz @r + +; If the highest bit is set, add a zero byte + shl eax, 1 + jnc @f + mov byte[edi], 0 + mov eax, [mpint_x] + inc dword[eax] + @@: + DEBUGM 1, "DH private key (x): ", [mpint_x] + +; Compute e = g^x mod p + stdcall mpint_modexp, [mpint_e], [mpint_g], [mpint_x], [mpint_p] + stdcall mpint_shrink, [mpint_e] + DEBUGM 1, "DH public key (e): ", [mpint_e] + +; Create group exchange init packet + mov byte[ebx + sshlib_connection.tx_buffer.message_code], SSH_MSG_KEX_DH_GEX_INIT + lea edi, [ebx + sshlib_connection.tx_buffer.message_code+1] + stdcall mpint_to_big_endian, edi, [mpint_e] + + DEBUGF 2, "Sending GEX init\n" + mov ecx, dword[ebx + sshlib_connection.tx_buffer.message_code+1] ;;;; dword[edi] + bswap ecx + add ecx, 5 + lea esi, [ebx + sshlib_connection.tx_buffer.message_code] + stdcall sshlib_send_packet, [con_ptr], esi, ecx, 0 + cmp eax, 0 + jl .err + +;--------------------------------------------- +; << Parse Diffie-Hellman Group Exchange Reply + + stdcall sshlib_recv_packet, [con_ptr], 0 + cmp eax, 0 + jl .err + + mov ebx, [con_ptr] + cmp [ebx + sshlib_connection.rx_buffer.message_code], SSH_MSG_KEX_DH_GEX_REPLY + jne .err_proto + + DEBUGF 2, "Received GEX Reply\n" + +;-------------------------------- +; HASH: string K_S, the host key + lea esi, [ebx + sshlib_connection.rx_buffer + sizeof.ssh_packet_header] + mov [str_K_S], esi + mov edx, [esi] + bswap edx + add edx, 4 + lea eax, [esi+edx] + mov [mpint_f_big], eax + invoke sha256_update, [temp_ctx], esi, edx + +;-------------------------------------------------------------------------- +; HASH: uint32 min, minimal size in bits of an acceptable group +; uint32 n, preferred size in bits of the group the server will send +; uint32 max, maximal size in bits of an acceptable group + invoke sha256_update, [temp_ctx], ssh_msg_gex_req+sizeof.ssh_packet_header-ssh_packet_header.message_code, 12 + +;---------------------------- +; HASH: mpint p, safe prime + stdcall mpint_shrink, [mpint_p] + stdcall mpint_to_big_endian, [mpint_tmp], [mpint_p] + add eax, 4 + invoke sha256_update, [temp_ctx], [mpint_tmp], eax + +;---------------------------------------- +; HASH: mpint g, generator for subgroup + stdcall mpint_shrink, [mpint_g] + stdcall mpint_to_big_endian, [mpint_tmp], [mpint_g] + add eax, 4 + invoke sha256_update, [temp_ctx], [mpint_tmp], eax + +;--------------------------------------------------- +; HASH: mpint e, exchange value sent by the client + mov ebx, [con_ptr] + lea esi, [ebx + sshlib_connection.tx_buffer + sizeof.ssh_packet_header] + mov edx, [esi] + bswap edx + add edx, 4 + invoke sha256_update, [temp_ctx], esi, edx + +;--------------------------------------------------- +; HASH: mpint f, exchange value sent by the server + mov esi, [mpint_f_big] + mov edx, [esi] + bswap edx + add edx, 4 + invoke sha256_update, [temp_ctx], esi, edx + + stdcall mpint_to_little_endian, [mpint_f], [mpint_f_big] + mov esi, [mpint_f_big] + add esi, eax + add esi, 4 + mov [str_s_of_H], esi + DEBUGM 1, "DH exchange value (f): ", [mpint_f] + +;-------------------------------------- +; Calculate shared secret K = f^x mod p + stdcall mpint_modexp, [mpint_tmp], [mpint_f], [mpint_x], [mpint_p] + stdcall mpint_shrink, [mpint_tmp] + DEBUGM 1, "DH shared secret (K): ", [mpint_tmp] + +; We always need it in big endian order, so store it as such. + stdcall mpint_to_big_endian, [mpint_K_big], [mpint_tmp] + mov [K_length], eax + +;----------------------------------- +; HASH: mpint K, the shared secret + add eax, 4 + invoke sha256_update, [temp_ctx], [mpint_K_big], eax + +;------------------------------- +; Finalize the exchange hash (H) + invoke sha256_final, [temp_ctx] + mov esi, [temp_ctx] + add esi, crash_ctx.hash + mov edi, [H] + mov ecx, SHA256_HASH_SIZE/4 + rep movsd + + DEBUGF 1, "Exchange hash H: " + stdcall dump_hex, [H], SHA256_HASH_SIZE/4 + +;-------------------------- +; Set or get the session id + + mov eax, [con_ptr] + cmp [eax + sshlib_connection.status], SSHLIB_CON_STAT_KEX_DONE + jae @f + +; If first KEX, verify host public key + stdcall sshlib_host_verify, [con_ptr], [str_K_S], [str_s_of_H], [H], SHA256_HASH_SIZE + test eax, eax + jnz .err + + mov eax, [con_ptr] + mov esi, [H] + lea edi, [eax + sshlib_connection.session_id] + mov ecx, SHA256_HASH_SIZE/4 + rep movsd + @@: + + lea esi, [eax + sshlib_connection.session_id] + lea edi, [session_id_x+1] + mov ecx, SHA256_HASH_SIZE/4 + rep movsd + + +;--------------- +; Calculate keys + +; First, calculate partial hash of K and H so we can re-use it for every key. + + invoke sha256_init, [k_h_ctx] + + mov ecx, [K_length] + add ecx, 4 + invoke sha256_update, [k_h_ctx], [mpint_K_big], ecx + invoke sha256_update, [k_h_ctx], [H], SHA256_HASH_SIZE + +;--------------------------------------------------------------- +; Initial IV client to server: HASH(K || H || "A" || session_id) + + mov esi, [k_h_ctx] + mov edi, [temp_ctx] + mov ecx, sizeof.crash_ctx/4 + rep movsd + lea edx, [session_id_x] + mov byte[edx], 'A' + invoke sha256_update, [temp_ctx], edx, SHA256_HASH_SIZE+1 + invoke sha256_final, [temp_ctx] + mov edi, [tx_iv] + mov esi, [temp_ctx] + mov ecx, SHA256_HASH_SIZE/4 + rep movsd + + DEBUGF 1, "Remote IV: " + stdcall dump_hex, [tx_iv], SHA256_HASH_SIZE/4 + +;--------------------------------------------------------------- +; Initial IV server to client: HASH(K || H || "B" || session_id) + + mov esi, [k_h_ctx] + mov edi, [temp_ctx] + mov ecx, sizeof.crash_ctx/4 + rep movsd + lea edx, [session_id_x] + mov byte[edx], 'B' + invoke sha256_update, [temp_ctx], edx, SHA256_HASH_SIZE+1 + invoke sha256_final, [temp_ctx] + mov edi, [rx_iv] + mov esi, [temp_ctx] + mov ecx, SHA256_HASH_SIZE/4 + rep movsd + + DEBUGF 1, "Local IV: " + stdcall dump_hex, [rx_iv], SHA256_HASH_SIZE/4 + +;------------------------------------------------------------------- +; Encryption key client to server: HASH(K || H || "C" || session_id) + + mov esi, [k_h_ctx] + mov edi, [temp_ctx] + mov ecx, sizeof.crash_ctx/4 + rep movsd + lea edx, [session_id_x] + mov byte[edx], 'C' + invoke sha256_update, [temp_ctx], edx, SHA256_HASH_SIZE+1 + invoke sha256_final, [temp_ctx] + mov edi, [tx_enc_key] + mov esi, [temp_ctx] + mov ecx, SHA256_HASH_SIZE/4 + rep movsd + + DEBUGF 1, "Remote key: " + stdcall dump_hex, [tx_enc_key], SHA256_HASH_SIZE/4 + +;------------------------------------------------------------------- +; Encryption key server to client: HASH(K || H || "D" || session_id) + + mov esi, [k_h_ctx] + mov edi, [temp_ctx] + mov ecx, sizeof.crash_ctx/4 + rep movsd + lea edx, [session_id_x] + mov byte[edx], 'D' + invoke sha256_update, [temp_ctx], edx, SHA256_HASH_SIZE+1 + invoke sha256_final, [temp_ctx] + mov edi, [rx_enc_key] + mov esi, [temp_ctx] + mov ecx, SHA256_HASH_SIZE/4 + rep movsd + + DEBUGF 1, "Local key: " + stdcall dump_hex, [rx_enc_key], SHA256_HASH_SIZE/4 + +;------------------------------------------------------------------ +; Integrity key client to server: HASH(K || H || "E" || session_id) + + mov esi, [k_h_ctx] + mov edi, [temp_ctx] + mov ecx, sizeof.crash_ctx/4 + rep movsd + lea edx, [session_id_x] + mov byte[edx], 'E' + invoke sha256_update, [temp_ctx], edx, SHA256_HASH_SIZE+1 + invoke sha256_final, [temp_ctx] + mov edi, [tx_int_key] + mov esi, [temp_ctx] + mov ecx, SHA256_HASH_SIZE/4 + rep movsd + + DEBUGF 1, "Remote Integrity key: " + stdcall dump_hex, [tx_int_key], SHA256_HASH_SIZE/4 + +;------------------------------------------------------------------ +; Integrity key server to client: HASH(K || H || "F" || session_id) + + mov esi, [k_h_ctx] + mov edi, [temp_ctx] + mov ecx, sizeof.crash_ctx/4 + rep movsd + lea edx, [session_id_x] + mov byte[edx], 'F' + invoke sha256_update, [temp_ctx], edx, SHA256_HASH_SIZE+1 + invoke sha256_final, [temp_ctx] + mov edi, [rx_int_key] + mov esi, [temp_ctx] + mov ecx, SHA256_HASH_SIZE/4 + rep movsd + + DEBUGF 1, "Local Integrity key: " + stdcall dump_hex, [rx_int_key] , SHA256_HASH_SIZE/4 + +;------------------------------------- +; << Parse Diffie-Hellman New Keys MSG + + stdcall sshlib_recv_packet, [con_ptr], 0 + cmp eax, 0 + jl .err + + mov ebx, [con_ptr] + cmp [ebx + sshlib_connection.rx_buffer.message_code], SSH_MSG_NEWKEYS + jne .err_proto + + DEBUGF 2, "Received New Keys\n" + +;------------------------------- +; >> Reply with New Keys message + + stdcall sshlib_send_packet, [con_ptr], ssh_msg_new_keys, ssh_msg_new_keys.length, 0 + cmp eax, 0 + jl .err + +;---------------------------------------------- +; Set keys and initialize transport subroutines + + DEBUGF 2, "SSH: Setting encryption keys\n" + + mov ebx, [con_ptr] + stdcall aes256_ctr_init, [rx_iv] + test eax, eax + jz .err_nomem + mov [ebx + sshlib_connection.rx_crypt_ctx_ptr], eax + stdcall aes256_set_encrypt_key, eax, [rx_enc_key] + mov [ebx + sshlib_connection.rx_crypt_proc], aes256_ctr_crypt + mov [ebx + sshlib_connection.rx_crypt_blocksize], AES256_BLOCKSIZE + + stdcall aes256_ctr_init, [tx_iv] + test eax, eax + jz .err_nomem + mov [ebx + sshlib_connection.tx_crypt_ctx_ptr], eax + stdcall aes256_set_encrypt_key, eax, [tx_enc_key] + mov [ebx + sshlib_connection.tx_crypt_proc], aes256_ctr_crypt + mov [ebx + sshlib_connection.tx_crypt_blocksize], AES256_BLOCKSIZE + + mov [ebx + sshlib_connection.tx_pad_size], AES256_BLOCKSIZE + mov [ebx + sshlib_connection.tx_pad_proc], MBRandom + + lea ecx, [ebx + sshlib_connection.rx_mac_ctx] + stdcall hmac_sha256_setkey, ecx, [rx_int_key], SHA256_HASH_SIZE + mov [ebx + sshlib_connection.rx_mac_proc], hmac_sha256 + mov [ebx + sshlib_connection.rx_mac_length], SHA256_HASH_SIZE + + lea ecx, [ebx + sshlib_connection.tx_mac_ctx] + stdcall hmac_sha256_setkey, ecx, [tx_int_key], SHA256_HASH_SIZE + mov [ebx + sshlib_connection.tx_mac_proc], hmac_sha256 + mov [ebx + sshlib_connection.tx_mac_length], SHA256_HASH_SIZE + + mov [ebx + sshlib_connection.status], SSHLIB_CON_STAT_KEX_DONE + xor eax, eax + + .err: + push eax + xor eax, eax + mov ecx, (7*(MAX_BITS/8+4) + 7*SHA256_HASH_SIZE + 2*sizeof.crash_ctx)/4 + mov edi, [mpint_tmp] + rep stosd + + mcall 68, 13, [mpint_tmp] + pop eax + ret + + .err_nomem: + DEBUGF 3, "Out of memory during key exchange!\n" + mov eax, SSHLIB_ERR_NOMEM + ret + + .err_proto: + DEBUGF 3, "Protocol error during key exchange!\n" + mov eax, SSHLIB_ERR_PROTOCOL + jmp .err + +endp diff --git a/programs/network/ssh/sshlib_host.inc b/programs/network/ssh/sshlib_host.inc new file mode 100644 index 0000000000..ae6a1efbb3 --- /dev/null +++ b/programs/network/ssh/sshlib_host.inc @@ -0,0 +1,243 @@ +; sshlib_host.inc - SSH remote host authentication +; +; Copyright (C) 2021 Jeffrey Amelynck +; +; This program is free software: you can redistribute it and/or modify +; it under the terms of the GNU General Public License as published by +; the Free Software Foundation, either version 3 of the License, or +; (at your option) any later version. +; +; This program is distributed in the hope that it will be useful, +; but WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +; GNU General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program. If not, see . + +; https://datatracker.ietf.org/doc/html/rfc4253#section-6.6 +; https://datatracker.ietf.org/doc/html/rfc3447 + +proc sshlib_host_verify con_ptr, str_host_key, str_signature, message, message_len + +locals + known_key_sz rb MAX_PUBLIC_KEY_SIZE +endl + + mov eax, [con_ptr] + cmp [eax+sshlib_connection.algo_hostkey], SSHLIB_HOSTKEY_RSA + je .rsa + ; ..add more here + mov eax, SSHLIB_ERR_HKEY_NO_ALGO + ret + + .rsa: + stdcall sshlib_host_verify_rsa, [str_host_key], [str_signature], [message], [message_len] + test eax, eax + jnz .err + + .lookup: +; lea eax, [known_key_sz] +; mov ebx, [con_ptr] +; lea ebx, [ebx + sshlib_connection.hostname_sz] +; invoke ini_get_str, known_hosts_file, ebx, ssh_rsa_sz, eax, MAX_PUBLIC_KEY_SIZE, null_sz +; test eax, eax +; jnz .unknown + +; TODO: verify cached host key +; jne .mismatch + + jmp .unknown ; FIXME + + xor eax, eax + ret + + .mismatch: + lea eax, [known_key_sz] + stdcall sshlib_callback_hostkey_problem, [con_ptr], SSHLIB_HOSTKEY_PROBLEM_MISMATCH, eax + cmp eax, SSHLIB_HOSTKEY_ACCEPT + je .store + ret + + .unknown: + lea eax, [known_key_sz] + stdcall sshlib_callback_hostkey_problem, [con_ptr], SSHLIB_HOSTKEY_PROBLEM_UNKNOWN, eax + cmp eax, SSHLIB_HOSTKEY_ACCEPT + je .store + ret + + .store: +; TODO: write to know_hosts file and fall-through + + ret + + .err: + ret + +endp + + +; https://datatracker.ietf.org/doc/html/rfc3447#section-8.2.2 +; RSASSA-PKCS1-V1_5-VERIFY +proc sshlib_host_verify_rsa str_host_key, str_signature, M, message_len + +locals + h_ctx dd ? + +; Signer's RSA public key + mpint_e dd ? ; public exponent + mpint_n dd ? ; modulus + + mpint_m dd ? + + EM dd ? + EM_accent dd ? + + mpint_s dd ? ; rsa_signature_blob + + +; k dd ? ; length of RSA modulus n + +endl + + DEBUGF 3, "SSH: Performing RSA verification\n" + + mcall 68, 12, sizeof.crash_ctx + 5*(MAX_BITS/8+4) + test eax, eax + jz .err_nomem + mov [h_ctx], eax + add eax, sizeof.crash_ctx + mov [mpint_e], eax + add eax, MAX_BITS/8+4 + mov [mpint_n], eax + add eax, MAX_BITS/8+4 + mov [mpint_m], eax + add eax, MAX_BITS/8+4 + mov [EM], eax + add eax, MAX_BITS/8+4 + mov [EM_accent], eax + add eax, MAX_BITS/8+4 + mov [mpint_s], eax +; add eax, MAX_BITS/8+4 + +; Host key + mov esi, [str_host_key] + mov ecx, [esi] + bswap ecx + cmp ecx, MAX_PUBLIC_KEY_SIZE + ja .err_key +; Host key type (string) + cmp dword[esi+4], 0x07000000 + jne .err_key + cmp dword[esi+8], 'ssh-' + jne .err_key + cmp dword[esi+11], '-rsa' + jne .err_key + add esi, 4+4+7 +; mpint e + stdcall mpint_to_little_endian, [mpint_e], esi + add esi, eax + add esi, 4 +; mpint n + stdcall mpint_to_little_endian, [mpint_n], esi +; mov [k], eax ;; HMMMM FIXME, 0-byte.. + +; Signature + mov esi, [str_signature] + mov ecx, [esi] + bswap ecx ; TODO: check length +; Host key type (string) + cmp dword[esi+4], 0x07000000 + jne .err_signature + cmp dword[esi+8], 'ssh-' + jne .err_signature + cmp dword[esi+11], '-rsa' + jne .err_signature + add esi, 4+4+7 +; RSA signature blob + stdcall mpint_to_little_endian, [mpint_s], esi +; cmp eax, [k] + ;;; jne .err_signature + +; RSAVP1 + stdcall mpint_modexp, [mpint_m], [mpint_s], [mpint_e], [mpint_n] +; I2OSP + stdcall mpint_shrink, [mpint_m] + stdcall mpint_grow, [mpint_m], 256 + stdcall mpint_to_big_endian, [EM], [mpint_m] + +; EMSA-PKCS1-v1_5 + invoke sha1_init, [h_ctx] + invoke sha1_update, [h_ctx], [M], [message_len] + invoke sha1_final, [h_ctx] + + mov edi, [EM_accent] + mov al, 0x00 + stosb + mov al, 0x01 + stosb + mov ecx, 256 - (rsa_sha1_t.len + 3 + SHA1_HASH_SIZE) + mov al, 0xff + rep stosb + mov al, 0x00 + stosb + mov esi, rsa_sha1_t + mov ecx, rsa_sha1_t.len + rep movsb + mov esi, [h_ctx] + mov ecx, SHA1_HASH_SIZE + rep movsb + +; Compare EM with EM_accent + mov esi, [EM] + add esi, 4 + mov edi, [EM_accent] + mov ecx, 256/4 + xor eax, eax + .ct_cmp_loop: + mov ebx, [esi] + xor ebx, [edi] + or eax, ebx + lea esi, [esi+4] + lea edi, [edi+4] + dec ecx + jnz .ct_cmp_loop + + push eax + mcall 68, 13, [h_ctx] + pop eax + + test eax, eax + jnz .fail + + DEBUGF 3, "SSH: RSA verification OK!\n" + + ret + + .fail: + DEBUGF 3, "SSH: RSA verification failed!\n" + mov eax, SSHLIB_ERR_HKEY_VERIFY_FAIL + ret + + .err_nomem: + mov eax, SSHLIB_ERR_NOMEM + ret + + .err_signature: + mov eax, SSHLIB_ERR_HKEY_SIGNATURE + ret + + .err_key: + mov eax, SSHLIB_ERR_HKEY_PUBLIC_KEY + ret + +endp + + +iglobal + + rsa_sha1_t db 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B, 0x0E, 0x03, 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14 + .len = $ - rsa_sha1_t + +endg + diff --git a/programs/network/ssh/mcodes.inc b/programs/network/ssh/sshlib_mcodes.inc similarity index 91% rename from programs/network/ssh/mcodes.inc rename to programs/network/ssh/sshlib_mcodes.inc index ef36074c0f..d3faff9817 100644 --- a/programs/network/ssh/mcodes.inc +++ b/programs/network/ssh/sshlib_mcodes.inc @@ -16,6 +16,7 @@ SSH_MSG_USERAUTH_REQUEST = 50 SSH_MSG_USERAUTH_FAILURE = 51 SSH_MSG_USERAUTH_SUCCESS = 52 SSH_MSG_USERAUTH_BANNER = 53 +SSH_MSG_USERAUTH_PASSWD_CHANGEREQ = 60 SSH_MSG_GLOBAL_REQUEST = 80 SSH_MSG_REQUEST_SUCCESS = 81 SSH_MSG_REQUEST_FAILURE = 82 diff --git a/programs/network/ssh/sshlib_transport.inc b/programs/network/ssh/sshlib_transport.inc new file mode 100644 index 0000000000..fd1edd884f --- /dev/null +++ b/programs/network/ssh/sshlib_transport.inc @@ -0,0 +1,275 @@ +; sshlib_transport.inc - SSH transport layer +; +; Copyright (C) 2016-2021 Jeffrey Amelynck +; +; This program is free software: you can redistribute it and/or modify +; it under the terms of the GNU General Public License as published by +; the Free Software Foundation, either version 3 of the License, or +; (at your option) any later version. +; +; This program is distributed in the hope that it will be useful, +; but WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +; GNU General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program. If not, see . + + +proc sshlib_padd_null + + xor eax, eax + ret + +endp + +proc sshlib_crypt_null ctx, src, dst + +; Assume src == dst ! + + ret + +endp + +proc sshlib_recv_packet con_ptr, flags + +locals + data_length dd ? ; Total length of packet without MAC +endl + + DEBUGF 3, "> " +; Receive first block (Read length, padding length, message code) + mov ebx, [con_ptr] + mov ecx, [ebx+sshlib_connection.socketnum] + mov esi, [ebx+sshlib_connection.rx_crypt_blocksize] + lea edx, [ebx+sshlib_connection.rx_buffer] + mov edi, [flags] + mcall recv + cmp eax, 0 + jle .sock_fail + sub [ssh_chan.rcv_wnd], eax ;;; FIXME + DEBUGF 1, "chunk = %u ", eax + mov ebx, [con_ptr] + cmp eax, [ebx+sshlib_connection.rx_crypt_blocksize] + jne .proto_fail ; TODO: handle receives of 1, 2, and 3 bytes correctly + +; Decrypt first block + pusha + lea esi, [ebx+sshlib_connection.rx_buffer] + stdcall [ebx+sshlib_connection.rx_crypt_proc], [ebx+sshlib_connection.rx_crypt_ctx_ptr], esi, esi + popa + +; Check data length + mov esi, [ebx + sshlib_connection.rx_buffer.packet_length] + bswap esi ; convert length to little endian + mov [ebx+sshlib_connection.rx_buffer.packet_length], esi + DEBUGF 1, "packet length=%u ", esi + cmp esi, BUFFERSIZE + ja .proto_fail ; packet is too large + +; Calculate amount of remaining data + add esi, 4 ; Packet length field itself is not included in the count + sub esi, [ebx+sshlib_connection.rx_crypt_blocksize] ; Already received this amount of data + add esi, [ebx+sshlib_connection.rx_mac_length] + jz .packet_complete + +; Receive remaining data + lea edx, [ebx+sshlib_connection.rx_buffer] + add edx, [ebx+sshlib_connection.rx_crypt_blocksize] + mov ecx, [ebx+sshlib_connection.socketnum] + mov edi, [flags] + .receive_loop: + DEBUGF 3, "want %d bytes.. ", esi + mcall recv + cmp eax, 0 + jle .sock_fail + sub [ssh_chan.rcv_wnd], eax ;;; FIXME + DEBUGF 3, "got %d bytes\n", eax + add edx, eax + sub esi, eax + jnz .receive_loop + +; Decrypt data + mov ebx, [con_ptr] + mov ecx, [ebx + sshlib_connection.rx_buffer.packet_length] + add ecx, 4 ; Packet_length field itself + sub ecx, [ebx+sshlib_connection.rx_crypt_blocksize] ; Already decrypted this amount of data + jz .decrypt_complete + + lea esi, [ebx+sshlib_connection.rx_buffer] + add esi, [ebx+sshlib_connection.rx_crypt_blocksize] + .decrypt_loop: + pusha + stdcall [ebx+sshlib_connection.rx_crypt_proc], [ebx+sshlib_connection.rx_crypt_ctx_ptr], esi, esi + popa + add esi, [ebx+sshlib_connection.rx_crypt_blocksize] + sub ecx, [ebx+sshlib_connection.rx_crypt_blocksize] + jnz .decrypt_loop + .decrypt_complete: + +; Authenticate message + cmp [ebx+sshlib_connection.rx_mac_proc], 0 + je .mac_complete + lea esi, [ebx+sshlib_connection.rx_mac_seqnr] + mov ecx, [ebx+sshlib_connection.rx_buffer.packet_length] + add ecx, 8 ; packet_length field itself + sequence number + lea eax, [ebx+sshlib_connection.rx_mac_ctx] +; push [ebx+sshlib_connection.rx_buffer.packet_length] + mov edx, [ebx+sshlib_connection.rx_buffer.packet_length] + bswap edx ; convert length to big endian + mov [ebx+sshlib_connection.rx_buffer.packet_length], edx + stdcall [ebx+sshlib_connection.rx_mac_proc], eax, esi, ecx +; pop [ebx+sshlib_connection.rx_buffer.packet_length] + mov edx, [ebx+sshlib_connection.rx_buffer.packet_length] + bswap edx ; convert length to little endian + mov [ebx+sshlib_connection.rx_buffer.packet_length], edx + + lea esi, [ebx+sshlib_connection.rx_mac_ctx] + lea edi, [ebx+sshlib_connection.rx_buffer+4] + add edi, [ebx+sshlib_connection.rx_buffer.packet_length] + mov ecx, [ebx+sshlib_connection.rx_mac_length] + shr ecx, 2 + repe cmpsd + jne .mac_fail + .mac_complete: + add byte[ebx+sshlib_connection.rx_mac_seqnr+3], 1 ; Update sequence counter + adc byte[ebx+sshlib_connection.rx_mac_seqnr+2], 0 + adc byte[ebx+sshlib_connection.rx_mac_seqnr+1], 0 + adc byte[ebx+sshlib_connection.rx_mac_seqnr+0], 0 + +; Return useful data length to the caller via eax register + .packet_complete: + mov eax, [ebx+sshlib_connection.rx_buffer.packet_length] + movzx ebx, [ebx+sshlib_connection.rx_buffer.padding_length] + sub eax, ebx + DEBUGF 1, "useful data length=%u\n", eax + ret + + .sock_fail: + DEBUGF 3, "ssh_recv_packet failed!\n" + mov eax, SSHLIB_ERR_SOCKET + ret + + .mac_fail: + DEBUGF 3, "ssh_recv_packet message authentication failed!\n" + mov eax, SSHLIB_ERR_MAC_VERIFY_FAIL + xor ebx, ebx + ret + + .proto_fail: + DEBUGF 3, "ssh_recv_packet protocol failure!\n" + mov eax, SSHLIB_ERR_PROTOCOL + xor ebx, ebx + ret + +endp + + +proc sshlib_send_packet con_ptr, buf, payload_size, flags + +locals + packet_size dd ? +endl + DEBUGF 2, "< " + +; Check how many bytes we should pad + mov eax, [payload_size] + inc eax ; padding length byte + lea edx, [eax+4] ; total packet size (without padding and MAC) + mov [packet_size], edx + + mov ecx, [con_ptr] + mov ebx, [ecx+sshlib_connection.tx_pad_size] + dec ebx + and edx, ebx + neg edx + add edx, [ecx+sshlib_connection.tx_pad_size] + add edx, [ecx+sshlib_connection.tx_pad_size] + DEBUGF 1, "padding %u bytes ", edx + add [packet_size], edx ; total packet size with padding + +; Start building the packet +; First comes the packet length, in network byte order ofcourse. + add eax, edx + DEBUGF 1, "total size: %u ", eax + bswap eax + lea edi, [ecx+sshlib_connection.tx_buffer] + stosd +; Then the padding length + mov al, dl + stosb +; And the actual payload bytes + mov esi, [buf] + mov ecx, [payload_size] + rep movsb + +; Append the packet with #edx padding bytes. +; Since we must pad at least 8 bytes, we can always use DWORD writes. +; First do an (unaligned) write exactly following the data + dec edx + mov esi, edx + shr esi, 2 ; number dwords + mov ebx, edx + and ebx, 3 + inc ebx ; number bytes in first write (1-4) + mov edx, [con_ptr] + call [edx+sshlib_connection.tx_pad_proc] + mov dword[edi], eax + add edi, ebx +; Then, do as many aligned writes as nescessary + mov ebx, [con_ptr] + @@: + call [ebx+sshlib_connection.tx_pad_proc] + stosd + dec esi + jnz @r + +; Append the packet with Message Authentication Code + mov edx, [con_ptr] + cmp [edx+sshlib_connection.tx_mac_proc], 0 + je .mac_complete + DEBUGF 1, "MAC sequence number: 0x%x\n", [edx+sshlib_connection.tx_mac_seqnr] + lea esi, [edx+sshlib_connection.tx_mac_seqnr] + mov ecx, [packet_size] + add ecx, 4 ; Sequence number length + lea eax, [edx+sshlib_connection.tx_mac_ctx] + stdcall [edx+sshlib_connection.tx_mac_proc], eax, esi, ecx + + lea esi, [edx+sshlib_connection.tx_mac_ctx] + lea edi, [edx+sshlib_connection.tx_buffer] + add edi, [packet_size] + mov ecx, [edx+sshlib_connection.tx_mac_length] + shr ecx, 2 + rep movsd + .mac_complete: + add byte[edx+sshlib_connection.tx_mac_seqnr+3], 1 ; Update sequence counter + adc byte[edx+sshlib_connection.tx_mac_seqnr+2], 0 + adc byte[edx+sshlib_connection.tx_mac_seqnr+1], 0 + adc byte[edx+sshlib_connection.tx_mac_seqnr+0], 0 + +; Now, encrypt everything but MAC + lea esi, [edx+sshlib_connection.tx_buffer] + mov ecx, [packet_size] + .encrypt_loop: + pusha + stdcall [edx+sshlib_connection.tx_crypt_proc], [edx+sshlib_connection.tx_crypt_ctx_ptr], esi, esi + popa + add esi, [edx+sshlib_connection.tx_crypt_blocksize] + sub ecx, [edx+sshlib_connection.tx_crypt_blocksize] + jnz .encrypt_loop + +; Send the packet + mov ebx, [con_ptr] + mov ecx, [ebx+sshlib_connection.socketnum] + lea edx, [ebx+sshlib_connection.tx_buffer] + mov esi, [packet_size] + add esi, [ebx+sshlib_connection.tx_mac_length] + mov edi, [flags] + mcall send + + DEBUGF 1, "\n" + + ret + +endp + diff --git a/programs/network/ssh/sshlib_userauth.inc b/programs/network/ssh/sshlib_userauth.inc new file mode 100644 index 0000000000..eb5ee4fe29 --- /dev/null +++ b/programs/network/ssh/sshlib_userauth.inc @@ -0,0 +1,149 @@ +; ssh_userauth.inc - SSH user authentication +; +; Copyright (C) 2021 Jeffrey Amelynck +; +; This program is free software: you can redistribute it and/or modify +; it under the terms of the GNU General Public License as published by +; the Free Software Foundation, either version 3 of the License, or +; (at your option) any later version. +; +; This program is distributed in the hope that it will be useful, +; but WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +; GNU General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program. If not, see . + + +proc sshlib_userauth_password con_ptr, username_sz, password_sz + +; >> Request service (user-auth) + + DEBUGF 2, "SSH: Requesting service\n" + + stdcall sshlib_send_packet, [con_ptr], ssh_msg_request_service, ssh_msg_request_service.length, 0 + cmp eax, 0 + jl .err + +; << Check for service acceptance + + stdcall sshlib_msg_handler, [con_ptr], 0 + cmp eax, 0 + jl .err + + mov eax, [con_ptr] + cmp [eax + sshlib_connection.rx_buffer.message_code], SSH_MSG_SERVICE_ACCEPT + jne .err_proto + +; >> Request user authentication + + DEBUGF 2, "SSH: User authentication\n" + + mcall 68, 12, 1024 ; FIXME: hardcoded size + test eax, eax + jz .err_nomem + mov edi, eax + mov ebx, eax + mov byte[edi], SSH_MSG_USERAUTH_REQUEST + inc edi + +; Insert username + stdcall sz_len, [username_sz] + mov ecx, eax + mov esi, [username_sz] + bswap eax + stosd + rep movsb + + mov dword[edi], 0x0e000000 ; 14 Bswapped + mov dword[edi+4], "ssh-" + mov dword[edi+8], "conn" + mov dword[edi+12], "ecti" + mov word[edi+16], "on" + add edi, 18 + + mov dword[edi], 0x08000000 ; 8 Bswapped + mov dword[edi+4], "pass" + mov dword[edi+8], "word" + + mov byte[edi+12], 0 ; bool + add edi, 13 + +; Insert password + stdcall sz_len, [password_sz] + mov ecx, eax + mov esi, [password_sz] + bswap eax + stosd + rep movsb + + sub edi, ebx + push ebx + stdcall sshlib_send_packet, [con_ptr], ebx, edi, 0 + +; Clear used buffer and free + pop edx + mov edi, edx + push eax + mov ecx, 1024/4 ; FIXME + xor eax, eax + rep stosd + mcall 68, 13, edx + pop eax + + cmp eax, 0 + jl .err + +; << Check for userauth acceptance + @@: + stdcall sshlib_msg_handler, [con_ptr], 0 + cmp eax, 0 + jl .err + + mov eax, [con_ptr] + mov al, [eax + sshlib_connection.rx_buffer.message_code] + + cmp al, SSH_MSG_USERAUTH_BANNER + je @r ; TODO + + cmp al, SSH_MSG_USERAUTH_FAILURE + je .fail + + cmp al, SSH_MSG_USERAUTH_SUCCESS + jne .err_proto + + xor eax, eax + .err: + ret + + .fail: + xor eax, eax + inc eax + ret + + .err_proto: + mov eax, SSHLIB_ERR_PROTOCOL + ret + + .err_nomem: + mov eax, SSHLIB_ERR_NOMEM + ret + + +endp + + +; Actually, string is \n and/or \0 terminated 0_o +proc sz_len uses ecx edi, string + + mov edi, [string] + mov ecx, 256 ;;;; + mov al, 10 + repne scasb + dec edi + sub edi, [string] + mov eax, edi + ret + +endp \ No newline at end of file