Big refactor: separate backend from frontend. Prepare for dynamically negotiated algorithms etc.

New: RSA host authentication, use new con_get_input from console.lib to get escape codes from special keys, UTF8 to CP866 decoder, ..
Bugfix: CTR counters.

git-svn-id: svn://kolibrios.org@9106 a494cfbc-eb01-0410-851d-a64ba20cac60
This commit is contained in:
hidnplayr 2021-08-02 18:40:01 +00:00
parent ec273ce904
commit 67b03ef814
14 changed files with 2479 additions and 1329 deletions

View File

@ -16,12 +16,15 @@
; along with this program. If not, see <http://www.gnu.org/licenses/>.
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

View File

@ -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

View File

@ -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 <http://www.gnu.org/licenses/>.
; 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

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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 <http://www.gnu.org/licenses/>.
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

View File

@ -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 <http://www.gnu.org/licenses/>.
; 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

View File

@ -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 <http://www.gnu.org/licenses/>.
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

View File

@ -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 <http://www.gnu.org/licenses/>.
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

View File

@ -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 <http://www.gnu.org/licenses/>.
; 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

View File

@ -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 <http://www.gnu.org/licenses/>.
; 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

View File

@ -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

View File

@ -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 <http://www.gnu.org/licenses/>.
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

View File

@ -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 <http://www.gnu.org/licenses/>.
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