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