From 8293967f0d98cdff213f4f330142297cdd714e42 Mon Sep 17 00:00:00 2001 From: tema5002 Date: Sun, 9 Aug 2026 19:55:45 +0300 Subject: [PATCH] moving onwards --- ssh.lua | 140 +++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 118 insertions(+), 22 deletions(-) diff --git a/ssh.lua b/ssh.lua index b5a5c06..041a3d4 100644 --- a/ssh.lua +++ b/ssh.lua @@ -4,9 +4,6 @@ local program_string = program_name .. "_" .. program_version local component = require("component") local internet = component.internet -local event = require("event") -local computer = require("computer") -local serialize = require("serialize") local function lazy_require(name) local this = {} @@ -16,15 +13,16 @@ local function lazy_require(name) module = module or require(name) return module[key] end, - __call = function(_, ...) - module = module or require(name) - return module(...) - end + __call = function(_, ...) + module = module or require(name) + return module(...) + end }) return this end -local curve25519 = lazy_require("curve25519") +local x25519 = lazy_require("x25519") +local sha2 = lazy_require("sha256") local function parse_ssh_version(str) local proto, software, comments = str:match("^SSH%-(%d+%.%d+)%-(%S+)%s?(.*)\r\n$") @@ -58,6 +56,79 @@ local SSH_MSG_SERVICE_ACCEPT = 6 local SSH_MSG_KEXINIT = 20 local SSH_MSG_NEWKEYS = 21 +-- RFC 4253 7.1: first entry in client list that also appears in server_list wins +local function negotiate_one(client_list, server_list) + local server_set = {} + for _, name in ipairs(server_list) do server_set[name] = true end + for _, name in ipairs(client_list) do + if server_set[name] then return name end + end + return nil +end + +local function negotiate(client_algs, server_kex) + local chosen = {} + for field, _ in pairs(client_algs) do + chosen[field] = negotiate_one(client_algs[field] or {}, server_kex[field] or {}) + local is_language_field = field:find("^languages_") + if not chosen[field] and not is_language_field then + return nil, "no agreement on " .. field + end + end + return chosen +end + +local function xor_with_byte(s, b) + local out = {} + for i = 1, #s do + out[i] = string.char(string.byte(s, i) ~ b) + end + return table.concat(out) +end + +local function hmac(hash_fn, block_size, key, message) + if #key > block_size then key = hash_fn(key) end + if #key < block_size then key = key .. string.rep('\0', block_size - #key) end + return hash_fn(xor_with_byte(key, 0x5c) .. hash_fn(xor_with_byte(key, 0x36) .. message)) +end + +local mac_algorithms_priority = { + "hmac-sha2-256", + "hmac-sha2-512", + --"hmac-sha1" +} + +local mac_algorithms = { + ["hmac-sha2-256"] = function(key, message) return hmac(sha2.sha256, 64, key, message) end, + ["hmac-sha2-512"] = function(key, message) return hmac(sha2.sha512, 128, key, message) end, + --["hmac-sha1"] = function(key, message) return hmac(sha1, 64, key, message) end, +} + +local encryption_algorithms_priority = { + "chacha20-poly1305@openssh.com", +-- aes128-gcm@openssh.com +-- aes256-gcm@openssh.com +-- aes128-ctr +-- aes192-ctr +-- aes256-ctr +} +local encryption_algorithms = { + ["chacha20-poly1305@openssh.com"] = nil +} + +local server_host_key_algorithms_priority = { + --[[ + "rsa-sha2-512", + "rsa-sha2-256", + "ecdsa-sha2-nistp256", + "ssh-ed25519" + ]] +} + +local server_host_key_algorithms = { + +} + local function socket() local this = {} local _sock @@ -89,7 +160,11 @@ local function socket() while #_read_buf < n do local chunk, err = _sock.read(n - #_read_buf) if chunk == nil then return nil, err end - _read_buf = _read_buf .. chunk + if chunk == "" then + coroutine.yield() + else + _read_buf = _read_buf .. chunk + end end local to_return = _read_buf:sub(1, n); _read_buf = _read_buf:sub(n + 1, -1) @@ -239,26 +314,47 @@ end local sock = socket() sock.connect("localhost", 22) -- RFC 4253: The maximum length of the string is 255 characters -local server_protoversion, server_softwareversion, server_comments = parse_ssh_version(sock.read_until_crlf(255)) +local server_version_raw = sock.read_until_crlf(255) +local server_protoversion, server_softwareversion, server_comments = parse_ssh_version(server_version_raw) if server_protoversion ~= "2.0" then return end -sock.write(encode_ssh_version("2.0", program_string)) -print(sock.parse_kexinit(sock.read_packet())) ---[[ + +local client_version_line = encode_ssh_version("2.0", program_string) +sock.write(client_version_line) + +local V_C = client_version_line:gsub("\r\n$", "") +local V_S = server_version_raw:gsub("\r\n$", "") + +local I_S, kerr = sock.read_packet() +if not I_S then + print("failed to read server KEXINIT: " .. tostring(kerr)) + return +end +local server_kex, perr = sock.parse_kexinit(I_S) +if not server_kex then + print("failed to parse server KEXINIT: " .. tostring(perr)) + return +end + local my_algs = { - kex_algorithms = {}, - server_host_key_algorithms = {}, - encryption_algorithms_client_to_server = {}, - encryption_algorithms_server_to_client = {}, - mac_algorithms_client_to_server = {}, - mac_algorithms_server_to_client = {}, + kex_algorithms = { "curve25519-sha256" }, + server_host_key_algorithms = server_host_key_algorithms, + encryption_algorithms_client_to_server = encryption_algorithms_priority, + encryption_algorithms_server_to_client = encryption_algorithms_priority, + mac_algorithms_client_to_server = mac_algorithms_priority, + mac_algorithms_server_to_client = mac_algorithms_priority, compression_algorithms_client_to_server = { "none" }, compression_algorithms_server_to_client = { "none" }, languages_client_to_server = {}, languages_server_to_client = {}, } -local my_kexinit_payload = sock.build_kexinit(my_algs) -sock.write_packet(my_kexinit_payload) -]] \ No newline at end of file +local I_C = sock.build_kexinit(my_algs) +sock.write_packet(I_C) + +local chosen, nerr = negotiate(my_algs, server_kex) +if not chosen then + print("algorithm negotiation failed: " .. tostring(nerr)) + return +end