kolibrios/programs/network/ssh/sshlib_connection.inc
hidnplayr cc6df1e340 Added support for encrypt-then-mac modes (hmac-sha2-256-etm,hmac-sha2-512-etm)
git-svn-id: svn://kolibrios.org@9990 a494cfbc-eb01-0410-851d-a64ba20cac60
2024-03-09 20:05:21 +00:00

569 lines
20 KiB
PHP

; sshlib_connection.inc - SSH connection
;
; Copyright (C) 2016-2024 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/rfc4253.txt
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 sha2_256.init, [ctx_ptr]
; HASH: string V_C, the client's version string (CR and NL excluded)
invoke sha2_256.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 sha2_256.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], 0
mov [eax + sshlib_connection.tx_crypt_proc], 0
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.rx_proc], sshlib_recv_packet_clear
mov [eax + sshlib_connection.tx_proc], sshlib_send_packet_clear
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 sha2_256.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 ; SSHLIB_CRYPT_CHACHA20_POLY1305
mov [esi+sshlib_connection.algo_mac_rx], SSHLIB_HMAC_SHA2_256_ETM
mov [esi+sshlib_connection.algo_mac_tx], SSHLIB_HMAC_SHA2_256_ETM
mov [esi+sshlib_connection.algo_compr_rx], SSHLIB_ALGO_NONE
mov [esi+sshlib_connection.algo_compr_tx], SSHLIB_ALGO_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 sha2_256.update, [ctx_ptr], esi, edx
; Exchange keys with the server
stdcall sshlib_dh_gex, [con_ptr]
test eax, eax
jnz .err
; Set keys and initialize transport subroutines
DEBUGF 2, "SSH: Setting encryption keys\n"
mov ebx, [con_ptr]
cmp [ebx + sshlib_connection.algo_crypt_rx], SSHLIB_CRYPT_AES256_CTR
je .rx_crypt_aes256_ctr
cmp [ebx + sshlib_connection.algo_crypt_rx], SSHLIB_CRYPT_AES256_CBC
je .rx_crypt_aes256_cbc
cmp [ebx + sshlib_connection.algo_crypt_rx], SSHLIB_CRYPT_CHACHA20_POLY1305
je .rx_crypt_poly1305_chacha20
jmp .err_proto
.rx_crypt_aes256_ctr:
lea ecx, [ebx + sshlib_connection.rx_crypt_ctx]
lea edx, [ebx + sshlib_connection.rx_enc_key]
lea esi, [ebx + sshlib_connection.rx_iv]
invoke aes256ctr.init, ecx, edx, esi, 0
push [aes256ctr.update]
pop [ebx + sshlib_connection.rx_crypt_proc]
mov [ebx + sshlib_connection.rx_crypt_blocksize], 16 ; AES_BLOCKSIZE
jmp .have_rx_crypt
.rx_crypt_aes256_cbc:
lea ecx, [ebx + sshlib_connection.rx_crypt_ctx]
lea edx, [ebx + sshlib_connection.rx_enc_key]
lea esi, [ebx + sshlib_connection.rx_iv]
invoke aes256cbc.init, ecx, edx, esi, 0
push [aes256cbc.update]
pop [ebx + sshlib_connection.rx_crypt_proc]
mov [ebx + sshlib_connection.rx_crypt_blocksize], 16 ; AES_BLOCKSIZE
jmp .have_rx_crypt
.rx_crypt_poly1305_chacha20:
mov [ebx + sshlib_connection.rx_proc], sshlib_recv_packet_poly1305chacha20
jmp .have_rx_crypt_and_mac
.have_rx_crypt:
cmp [ebx + sshlib_connection.algo_mac_rx], SSHLIB_HMAC_SHA2_256
je .rx_hmac_sha2_256
cmp [ebx + sshlib_connection.algo_mac_rx], SSHLIB_HMAC_SHA2_512
je .rx_hmac_sha2_512
cmp [ebx + sshlib_connection.algo_mac_rx], SSHLIB_HMAC_SHA2_256_ETM
je .rx_hmac_sha2_256_etm
cmp [ebx + sshlib_connection.algo_mac_rx], SSHLIB_HMAC_SHA2_512_ETM
je .rx_hmac_sha2_512_etm
jmp .err_proto
.rx_hmac_sha2_256:
push [hmac_sha2_256.oneshot]
pop [ebx + sshlib_connection.rx_mac_proc]
mov [ebx + sshlib_connection.rx_mac_length], SHA2_256_LEN
mov [ebx + sshlib_connection.rx_proc], sshlib_recv_packet_hmac
jmp .have_rx_crypt_and_mac
.rx_hmac_sha2_512:
push [hmac_sha2_512.oneshot]
pop [ebx + sshlib_connection.rx_mac_proc]
mov [ebx + sshlib_connection.rx_mac_length], SHA2_512_LEN
mov [ebx + sshlib_connection.rx_proc], sshlib_recv_packet_hmac
jmp .have_rx_crypt_and_mac
.rx_hmac_sha2_256_etm:
push [hmac_sha2_256.oneshot]
pop [ebx + sshlib_connection.rx_mac_proc]
mov [ebx + sshlib_connection.rx_mac_length], SHA2_256_LEN
mov [ebx + sshlib_connection.rx_proc], sshlib_recv_packet_hmac_etm
jmp .have_rx_crypt_and_mac
.rx_hmac_sha2_512_etm:
push [hmac_sha2_512.oneshot]
pop [ebx + sshlib_connection.rx_mac_proc]
mov [ebx + sshlib_connection.rx_mac_length], SHA2_512_LEN
mov [ebx + sshlib_connection.rx_proc], sshlib_recv_packet_hmac_etm
jmp .have_rx_crypt_and_mac
.have_rx_crypt_and_mac:
cmp [ebx + sshlib_connection.algo_crypt_tx], SSHLIB_CRYPT_AES256_CTR
je .tx_crypt_aes256_ctr
cmp [ebx + sshlib_connection.algo_crypt_tx], SSHLIB_CRYPT_AES256_CBC
je .tx_crypt_aes256_cbc
cmp [ebx + sshlib_connection.algo_crypt_tx], SSHLIB_CRYPT_CHACHA20_POLY1305
je .tx_crypt_poly1305_chacha20
jmp .err_proto
.tx_crypt_aes256_ctr:
lea ecx, [ebx + sshlib_connection.tx_crypt_ctx]
lea edx, [ebx + sshlib_connection.tx_enc_key]
lea esi, [ebx + sshlib_connection.tx_iv]
invoke aes256ctr.init, ecx, edx, esi, 0
push [aes256ctr.update]
pop [ebx + sshlib_connection.tx_crypt_proc]
mov [ebx + sshlib_connection.tx_crypt_blocksize], 16 ; AES_BLOCKSIZE
mov [ebx + sshlib_connection.tx_pad_size], 16 ; AES_BLOCKSIZE
jmp .have_tx_crypt
.tx_crypt_aes256_cbc:
lea ecx, [ebx + sshlib_connection.tx_crypt_ctx]
lea edx, [ebx + sshlib_connection.tx_enc_key]
lea esi, [ebx + sshlib_connection.tx_iv]
invoke aes256cbc.init, ecx, edx, esi, 0
push [aes256cbc.update]
pop [ebx + sshlib_connection.tx_crypt_proc]
mov [ebx + sshlib_connection.tx_crypt_blocksize], 16 ; AES_BLOCKSIZE
mov [ebx + sshlib_connection.tx_pad_size], 16 ; AES_BLOCKSIZE
jmp .have_tx_crypt
.tx_crypt_poly1305_chacha20:
mov [ebx + sshlib_connection.tx_proc], sshlib_send_packet_poly1305chacha20
jmp .have_tx_crypt_and_mac
.have_tx_crypt:
cmp [ebx + sshlib_connection.algo_mac_tx], SSHLIB_HMAC_SHA2_256
je .tx_hmac_sha2_256
cmp [ebx + sshlib_connection.algo_mac_tx], SSHLIB_HMAC_SHA2_512
je .tx_hmac_sha2_512
cmp [ebx + sshlib_connection.algo_mac_tx], SSHLIB_HMAC_SHA2_256_ETM
je .tx_hmac_sha2_256_etm
cmp [ebx + sshlib_connection.algo_mac_tx], SSHLIB_HMAC_SHA2_512_ETM
je .tx_hmac_sha2_512_etm
jmp .err_proto
.tx_hmac_sha2_256:
push [hmac_sha2_256.oneshot]
pop [ebx + sshlib_connection.tx_mac_proc]
mov [ebx + sshlib_connection.tx_mac_length], SHA2_256_LEN
mov [ebx + sshlib_connection.tx_proc], sshlib_send_packet_hmac
jmp .have_tx_crypt_and_mac
.tx_hmac_sha2_512:
push [hmac_sha2_512.oneshot]
pop [ebx + sshlib_connection.tx_mac_proc]
mov [ebx + sshlib_connection.tx_mac_length], SHA2_512_LEN
mov [ebx + sshlib_connection.tx_proc], sshlib_send_packet_hmac
jmp .have_tx_crypt_and_mac
.tx_hmac_sha2_256_etm:
push [hmac_sha2_256.oneshot]
pop [ebx + sshlib_connection.tx_mac_proc]
mov [ebx + sshlib_connection.tx_mac_length], SHA2_256_LEN
mov [ebx + sshlib_connection.tx_proc], sshlib_send_packet_hmac_etm
jmp .have_tx_crypt_and_mac
.tx_hmac_sha2_512_etm:
push [hmac_sha2_512.oneshot]
pop [ebx + sshlib_connection.tx_mac_proc]
mov [ebx + sshlib_connection.tx_mac_length], SHA2_512_LEN
mov [ebx + sshlib_connection.tx_proc], sshlib_send_packet_hmac_etm
jmp .have_tx_crypt_and_mac
.have_tx_crypt_and_mac:
; 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]
add esi, sizeof.ssh_packet_header
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
DEBUGF 3, "SSH: Message type: %u\n", al
.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:
add esi, 4
DEBUGF 3, "SSH: Global MSG received: %s\n", esi
;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