From eb12eb7a818aaf3798c2dfb760b8583dbc222d98 Mon Sep 17 00:00:00 2001 From: tema5002 Date: Sun, 9 Aug 2026 13:54:08 +0300 Subject: [PATCH] Initial commit --- LICENSE | 22 ++ curve25519.lua | 566 +++++++++++++++++++++++++++++++++++++++++++++++++ run.lua | 39 ++++ ssh.lua | 264 +++++++++++++++++++++++ 4 files changed, 891 insertions(+) create mode 100644 LICENSE create mode 100644 curve25519.lua create mode 100755 run.lua create mode 100644 ssh.lua diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8f657fb --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright © 2026 tema5002 + + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/curve25519.lua b/curve25519.lua new file mode 100644 index 0000000..0166e2f --- /dev/null +++ b/curve25519.lua @@ -0,0 +1,566 @@ +-- local curve25519 = require("curve25519") +-- curve25519(private_key, peer_public_value) + +if not load("return (1>>1) == 0 and (1<<1) == 2 and (1&1) == 1 and (1|0) == 1") then + error("bitwise operators not available; need Lua 5.3+") +end + +local kBottom25Bits = 0x1ffffff +local kBottom26Bits = 0x3ffffff +local kTop39Bits = 0xfffffffffe000000 +local kTop38Bits = 0xfffffffffc000000 + +local function load_3(i, offset) + return i[1 + offset] | (i[2 + offset] << 8) | (i[3 + offset] << 16) +end + +local function load_4(i, offset) + return i[1 + offset] | (i[2 + offset] << 8) | (i[3 + offset] << 16) | (i[4 + offset] << 24) +end + +local function arshift(x, n) + n = n & 63 + if n == 0 then return x end + if x >= 0 then + return x >> n + else + return (x >> n) | (~0 << (64 - n)) + end +end + +local function fe_frombytes(s) + local h = { + load_4(s, 0), + load_3(s, 4) << 6, + load_3(s, 7) << 5, + load_3(s, 10) << 3, + load_3(s, 13) << 2, + load_4(s, 16), + load_3(s, 20) << 7, + load_3(s, 23) << 5, + load_3(s, 26) << 4, + (load_3(s, 29) & 0x7fffff) << 2 + } + local carry + carry = h[10] + (1 << 24); h[1] = h[1] + arshift(carry, 25) * 19; h[10] = h[10] - (carry & kTop39Bits) + carry = h[2] + (1 << 24); h[3] = h[3] + arshift(carry, 25); h[2] = h[2] - (carry & kTop39Bits) + carry = h[4] + (1 << 24); h[5] = h[5] + arshift(carry, 25); h[4] = h[4] - (carry & kTop39Bits) + carry = h[6] + (1 << 24); h[7] = h[7] + arshift(carry, 25); h[6] = h[6] - (carry & kTop39Bits) + carry = h[8] + (1 << 24); h[9] = h[9] + arshift(carry, 25); h[8] = h[8] - (carry & kTop39Bits) + carry = h[1] + (1 << 25); h[2] = h[2] + arshift(carry, 26); h[1] = h[1] - (carry & kTop38Bits) + carry = h[3] + (1 << 25); h[4] = h[4] + arshift(carry, 26); h[3] = h[3] - (carry & kTop38Bits) + carry = h[5] + (1 << 25); h[6] = h[6] + arshift(carry, 26); h[5] = h[5] - (carry & kTop38Bits) + carry = h[7] + (1 << 25); h[8] = h[8] + arshift(carry, 26); h[7] = h[7] - (carry & kTop38Bits) + carry = h[9] + (1 << 25); h[10] = h[10] + arshift(carry, 26); h[9] = h[9] - (carry & kTop38Bits) + return h +end + +local function fe_tobytes(h) + local q + q = (19 * h[10] + (1 << 24)) >> 25 + q = arshift(h[1] + q, 26) + q = arshift(h[2] + q, 25) + q = arshift(h[3] + q, 26) + q = arshift(h[4] + q, 25) + q = arshift(h[5] + q, 26) + q = arshift(h[6] + q, 25) + q = arshift(h[7] + q, 26) + q = arshift(h[8] + q, 25) + q = arshift(h[9] + q, 26) + q = arshift(h[10] + q, 25) + + h[1] = h[1] + 19 * q + h[2] = h[2] + arshift(h[1], 26) + h[1] = h[1] & kBottom26Bits + h[3] = h[3] + arshift(h[2], 25) + h[2] = h[2] & kBottom25Bits + h[4] = h[4] + arshift(h[3], 26) + h[3] = h[3] & kBottom26Bits + h[5] = h[5] + arshift(h[4], 25) + h[4] = h[4] & kBottom25Bits + h[6] = h[6] + arshift(h[5], 26) + h[5] = h[5] & kBottom26Bits + h[7] = h[7] + arshift(h[6], 25) + h[6] = h[6] & kBottom25Bits + h[8] = h[8] + arshift(h[7], 26) + h[7] = h[7] & kBottom26Bits + h[9] = h[9] + arshift(h[8], 25) + h[8] = h[8] & kBottom25Bits + h[10] = h[10] + arshift(h[9], 26) + h[9] = h[9] & kBottom26Bits + h[10] = h[10] & kBottom25Bits + + return { + (h[1] >> 0) & 0xff, + (h[1] >> 8) & 0xff, + (h[1] >> 16) & 0xff, + ((h[1] >> 24) | (h[2] << 2)) & 0xff, + (h[2] >> 6) & 0xff, + (h[2] >> 14) & 0xff, + ((h[2] >> 22) | (h[3] << 3)) & 0xff, + (h[3] >> 5) & 0xff, + (h[3] >> 13) & 0xff, + ((h[3] >> 21) | (h[4] << 5)) & 0xff, + (h[4] >> 3) & 0xff, + (h[4] >> 11) & 0xff, + ((h[4] >> 19) | (h[5] << 6)) & 0xff, + (h[5] >> 2) & 0xff, + (h[5] >> 10) & 0xff, + (h[5] >> 18) & 0xff, + (h[6] >> 0) & 0xff, + (h[6] >> 8) & 0xff, + (h[6] >> 16) & 0xff, + ((h[6] >> 24) | (h[7] << 1)) & 0xff, + (h[7] >> 7) & 0xff, + (h[7] >> 15) & 0xff, + ((h[7] >> 23) | (h[8] << 3)) & 0xff, + (h[8] >> 5) & 0xff, + (h[8] >> 13) & 0xff, + ((h[8] >> 21) | (h[9] << 4)) & 0xff, + (h[9] >> 4) & 0xff, + (h[9] >> 12) & 0xff, + ((h[9] >> 20) | (h[10] << 6)) & 0xff, + (h[10] >> 2) & 0xff, + (h[10] >> 10) & 0xff, + (h[10] >> 18) & 0xff + } +end + +local function fe_add(f, g) + local h = {} + for i = 1, 10 do + h[i] = (f[i] + g[i]) + end + return h +end + +local function fe_addto(f, g) + for i = 1, 10 do + f[i] = (f[i] + g[i]) + end +end + +local function fe_sub(f, g) + local h = {} + for i = 1, 10 do + h[i] = (f[i] - g[i]) + end + return h +end + +local function fe_subto(f, g) + for i = 1, 10 do + f[i] = (f[i] - g[i]) + end +end + +local function fe_cswap(f, g, b) + b = 0 - b + for i = 1, 10 do + local x = f[i] ~ g[i] + x = x & b + f[i] = f[i] ~ x + g[i] = g[i] ~ x + end +end + +local function fe_mul121666(f) + local h = { + f[1] * 121666, + f[2] * 121666, + f[3] * 121666, + f[4] * 121666, + f[5] * 121666, + f[6] * 121666, + f[7] * 121666, + f[8] * 121666, + f[9] * 121666, + f[10] * 121666 + } + local carry + carry = h[10] + (1 << 24); h[1] = h[1] + arshift(carry, 25) * 19; h[10] = h[10] - (carry & kTop39Bits) + carry = h[2] + (1 << 24); h[3] = h[3] + arshift(carry, 25); h[2] = h[2] - (carry & kTop39Bits) + carry = h[4] + (1 << 24); h[5] = h[5] + arshift(carry, 25); h[4] = h[4] - (carry & kTop39Bits) + carry = h[6] + (1 << 24); h[7] = h[7] + arshift(carry, 25); h[6] = h[6] - (carry & kTop39Bits) + carry = h[8] + (1 << 24); h[9] = h[9] + arshift(carry, 25); h[8] = h[8] - (carry & kTop39Bits) + carry = h[1] + (1 << 25); h[2] = h[2] + arshift(carry, 26); h[1] = h[1] - (carry & kTop38Bits) + carry = h[3] + (1 << 25); h[4] = h[4] + arshift(carry, 26); h[3] = h[3] - (carry & kTop38Bits) + carry = h[5] + (1 << 25); h[6] = h[6] + arshift(carry, 26); h[5] = h[5] - (carry & kTop38Bits) + carry = h[7] + (1 << 25); h[8] = h[8] + arshift(carry, 26); h[7] = h[7] - (carry & kTop38Bits) + carry = h[9] + (1 << 25); h[10] = h[10] + arshift(carry, 26); h[9] = h[9] - (carry & kTop38Bits) + return h +end + +local function fe_mul(f, g) + local g2_19 = 19 * g[2] + local g3_19 = 19 * g[3] + local g4_19 = 19 * g[4] + local g5_19 = 19 * g[5] + local g6_19 = 19 * g[6] + local g7_19 = 19 * g[7] + local g8_19 = 19 * g[8] + local g9_19 = 19 * g[9] + local g10_19 = 19 * g[10] + + local f2_2 = 2 * f[2] + local f4_2 = 2 * f[4] + local f6_2 = 2 * f[6] + local f8_2 = 2 * f[8] + local f10_2 = 2 * f[10] + + local f1g1 = f[1] * g[1] + local f1g2 = f[1] * g[2] + local f1g3 = f[1] * g[3] + local f1g4 = f[1] * g[4] + local f1g5 = f[1] * g[5] + local f1g6 = f[1] * g[6] + local f1g7 = f[1] * g[7] + local f1g8 = f[1] * g[8] + local f1g9 = f[1] * g[9] + local f1g10 = f[1] * g[10] + + local f2g1 = f[2] * g[1] + local f2g2_2 = f2_2 * g[2] + local f2g3 = f[2] * g[3] + local f2g4_2 = f2_2 * g[4] + local f2g5 = f[2] * g[5] + local f2g6_2 = f2_2 * g[6] + local f2g7 = f[2] * g[7] + local f2g8_2 = f2_2 * g[8] + local f2g9 = f[2] * g[9] + local f2g10_38 = f2_2 * g10_19 + + local f3g1 = f[3] * g[1] + local f3g2 = f[3] * g[2] + local f3g3 = f[3] * g[3] + local f3g4 = f[3] * g[4] + local f3g5 = f[3] * g[5] + local f3g6 = f[3] * g[6] + local f3g7 = f[3] * g[7] + local f3g8 = f[3] * g[8] + local f3g9_19 = f[3] * g9_19 + local f3g10_19 = f[3] * g10_19 + + local f4g1 = f[4] * g[1] + local f4g2_2 = f4_2 * g[2] + local f4g3 = f[4] * g[3] + local f4g4_2 = f4_2 * g[4] + local f4g5 = f[4] * g[5] + local f4g6_2 = f4_2 * g[6] + local f4g7 = f[4] * g[7] + local f4g8_38 = f4_2 * g8_19 + local f4g9_19 = f[4] * g9_19 + local f4g10_38 = f4_2 * g10_19 + + local f5g1 = f[5] * g[1] + local f5g2 = f[5] * g[2] + local f5g3 = f[5] * g[3] + local f5g4 = f[5] * g[4] + local f5g5 = f[5] * g[5] + local f5g6 = f[5] * g[6] + local f5g7_19 = f[5] * g7_19 + local f5g8_19 = f[5] * g8_19 + local f5g9_19 = f[5] * g9_19 + local f5g10_19 = f[5] * g10_19 + + local f6g1 = f[6] * g[1] + local f6g2_2 = f6_2 * g[2] + local f6g3 = f[6] * g[3] + local f6g4_2 = f6_2 * g[4] + local f6g5 = f[6] * g[5] + local f6g6_38 = f6_2 * g6_19 + local f6g7_19 = f[6] * g7_19 + local f6g8_38 = f6_2 * g8_19 + local f6g9_19 = f[6] * g9_19 + local f6g10_38 = f6_2 * g10_19 + + local f7g1 = f[7] * g[1] + local f7g2 = f[7] * g[2] + local f7g3 = f[7] * g[3] + local f7g4 = f[7] * g[4] + local f7g5_19 = f[7] * g5_19 + local f7g6_19 = f[7] * g6_19 + local f7g7_19 = f[7] * g7_19 + local f7g8_19 = f[7] * g8_19 + local f7g9_19 = f[7] * g9_19 + local f7g10_19 = f[7] * g10_19 + + local f8g1 = f[8] * g[1] + local f8g2_2 = f8_2 * g[2] + local f8g3 = f[8] * g[3] + local f8g4_38 = f8_2 * g4_19 + local f8g5_19 = f[8] * g5_19 + local f8g6_38 = f8_2 * g6_19 + local f8g7_19 = f[8] * g7_19 + local f8g8_38 = f8_2 * g8_19 + local f8g9_19 = f[8] * g9_19 + local f8g10_38 = f8_2 * g10_19 + + local f9g1 = f[9] * g[1] + local f9g2 = f[9] * g[2] + local f9g3_19 = f[9] * g3_19 + local f9g4_19 = f[9] * g4_19 + local f9g5_19 = f[9] * g5_19 + local f9g6_19 = f[9] * g6_19 + local f9g7_19 = f[9] * g7_19 + local f9g8_19 = f[9] * g8_19 + local f9g9_19 = f[9] * g9_19 + local f9g10_19 = f[9] * g10_19 + + local f10g1 = f[10] * g[1] + local f10g2_38 = f10_2 * g2_19 + local f10g3_19 = f[10] * g3_19 + local f10g4_38 = f10_2 * g4_19 + local f10g5_19 = f[10] * g5_19 + local f10g6_38 = f10_2 * g6_19 + local f10g7_19 = f[10] * g7_19 + local f10g8_38 = f10_2 * g8_19 + local f10g9_19 = f[10] * g9_19 + local f10g10_38 = f10_2 * g10_19 + + local h = { + f1g1 + f2g10_38 + f3g9_19 + f4g8_38 + f5g7_19 + f6g6_38 + f7g5_19 + f8g4_38 + f9g3_19 + f10g2_38, + f1g2 + f2g1 + f3g10_19 + f4g9_19 + f5g8_19 + f6g7_19 + f7g6_19 + f8g5_19 + f9g4_19 + f10g3_19, + f1g3 + f2g2_2 + f3g1 + f4g10_38 + f5g9_19 + f6g8_38 + f7g7_19 + f8g6_38 + f9g5_19 + f10g4_38, + f1g4 + f2g3 + f3g2 + f4g1 + f5g10_19 + f6g9_19 + f7g8_19 + f8g7_19 + f9g6_19 + f10g5_19, + f1g5 + f2g4_2 + f3g3 + f4g2_2 + f5g1 + f6g10_38 + f7g9_19 + f8g8_38 + f9g7_19 + f10g6_38, + f1g6 + f2g5 + f3g4 + f4g3 + f5g2 + f6g1 + f7g10_19 + f8g9_19 + f9g8_19 + f10g7_19, + f1g7 + f2g6_2 + f3g5 + f4g4_2 + f5g3 + f6g2_2 + f7g1 + f8g10_38 + f9g9_19 + f10g8_38, + f1g8 + f2g7 + f3g6 + f4g5 + f5g4 + f6g3 + f7g2 + f8g1 + f9g10_19 + f10g9_19, + f1g9 + f2g8_2 + f3g7 + f4g6_2 + f5g5 + f6g4_2 + f7g3 + f8g2_2 + f9g1 + f10g10_38, + f1g10 + f2g9 + f3g8 + f4g7 + f5g6 + f6g5 + f7g4 + f8g3 + f9g2 + f10g1 + } + local carry + carry = h[1] + (1 << 25); h[2] = h[2] + arshift(carry, 26); h[1] = h[1] - (carry & kTop38Bits) + carry = h[5] + (1 << 25); h[6] = h[6] + arshift(carry, 26); h[5] = h[5] - (carry & kTop38Bits) + carry = h[2] + (1 << 24); h[3] = h[3] + arshift(carry, 25); h[2] = h[2] - (carry & kTop39Bits) + carry = h[6] + (1 << 24); h[7] = h[7] + arshift(carry, 25); h[6] = h[6] - (carry & kTop39Bits) + carry = h[3] + (1 << 25); h[4] = h[4] + arshift(carry, 26); h[3] = h[3] - (carry & kTop38Bits) + carry = h[7] + (1 << 25); h[8] = h[8] + arshift(carry, 26); h[7] = h[7] - (carry & kTop38Bits) + carry = h[4] + (1 << 24); h[5] = h[5] + arshift(carry, 25); h[4] = h[4] - (carry & kTop39Bits) + carry = h[8] + (1 << 24); h[9] = h[9] + arshift(carry, 25); h[8] = h[8] - (carry & kTop39Bits) + carry = h[5] + (1 << 25); h[6] = h[6] + arshift(carry, 26); h[5] = h[5] - (carry & kTop38Bits) + carry = h[9] + (1 << 25); h[10] = h[10] + arshift(carry, 26); h[9] = h[9] - (carry & kTop38Bits) + carry = h[10] + (1 << 24); h[1] = h[1] + arshift(carry, 25) * 19; h[10] = h[10] - (carry & kTop39Bits) + carry = h[1] + (1 << 25); h[2] = h[2] + arshift(carry, 26); h[1] = h[1] - (carry & kTop38Bits) + return h +end + +local function fe_sq(f) + local f1_2 = 2 * f[1] + local f2_2 = 2 * f[2] + local f3_2 = 2 * f[3] + local f4_2 = 2 * f[4] + local f5_2 = 2 * f[5] + local f6_2 = 2 * f[6] + local f7_2 = 2 * f[7] + local f8_2 = 2 * f[8] + + local f6_38 = 38 * f[6] + local f7_19 = 19 * f[7] + local f8_38 = 38 * f[8] + local f9_19 = 19 * f[9] + local f10_38 = 38 * f[10] + + local f1f1 = f[1] * f[1] + local f1f2_2 = f1_2 * f[2] + local f1f3_2 = f1_2 * f[3] + local f1f4_2 = f1_2 * f[4] + local f1f5_2 = f1_2 * f[5] + local f1f6_2 = f1_2 * f[6] + local f1f7_2 = f1_2 * f[7] + local f1f8_2 = f1_2 * f[8] + local f1f9_2 = f1_2 * f[9] + local f1f10_2 = f1_2 * f[10] + + local f2f2_2 = f2_2 * f[2] + local f2f3_2 = f2_2 * f[3] + local f2f4_4 = f2_2 * f4_2 + local f2f5_2 = f2_2 * f[5] + local f2f6_4 = f2_2 * f6_2 + local f2f7_2 = f2_2 * f[7] + local f2f8_4 = f2_2 * f8_2 + local f2f9_2 = f2_2 * f[9] + local f2f10_76 = f2_2 * f10_38 + + local f3f3 = f[3] * f[3] + local f3f4_2 = f3_2 * f[4] + local f3f5_2 = f3_2 * f[5] + local f3f6_2 = f3_2 * f[6] + local f3f7_2 = f3_2 * f[7] + local f3f8_2 = f3_2 * f[8] + local f3f9_38 = f3_2 * f9_19 + local f3f10_38 = f[3] * f10_38 + + local f4f4_2 = f4_2 * f[4] + local f4f5_2 = f4_2 * f[5] + local f4f6_4 = f4_2 * f6_2 + local f4f7_2 = f4_2 * f[7] + local f4f8_76 = f4_2 * f8_38 + local f4f9_38 = f4_2 * f9_19 + local f4f10_76 = f4_2 * f10_38 + + local f5f5 = f[5] * f[5] + local f5f6_2 = f5_2 * f[6] + local f5f7_38 = f5_2 * f7_19 + local f5f8_38 = f[5] * f8_38 + local f5f9_38 = f5_2 * f9_19 + local f5f10_38 = f[5] * f10_38 + + local f6f6_38 = f[6] * f6_38 + local f6f7_38 = f6_2 * f7_19 + local f6f8_76 = f6_2 * f8_38 + local f6f9_38 = f6_2 * f9_19 + local f6f10_76 = f6_2 * f10_38 + + local f7f7_19 = f[7] * f7_19 + local f7f8_38 = f[7] * f8_38 + local f7f9_38 = f7_2 * f9_19 + local f7f10_38 = f[7] * f10_38 + + local f8f8_38 = f[8] * f8_38 + local f8f9_38 = f8_2 * f9_19 + local f8f10_76 = f8_2 * f10_38 + + local f9f9_19 = f[9] * f9_19 + local f9f10_38 = f[9] * f10_38 + + local f10f10_38 = f[10] * f10_38 + + local h = { + f1f1 + f2f10_76 + f3f9_38 + f4f8_76 + f5f7_38 + f6f6_38, + f1f2_2 + f3f10_38 + f4f9_38 + f5f8_38 + f6f7_38, + f1f3_2 + f2f2_2 + f4f10_76 + f5f9_38 + f6f8_76 + f7f7_19, + f1f4_2 + f2f3_2 + f5f10_38 + f6f9_38 + f7f8_38, + f1f5_2 + f2f4_4 + f3f3 + f6f10_76 + f7f9_38 + f8f8_38, + f1f6_2 + f2f5_2 + f3f4_2 + f7f10_38 + f8f9_38, + f1f7_2 + f2f6_4 + f3f5_2 + f4f4_2 + f8f10_76 + f9f9_19, + f1f8_2 + f2f7_2 + f3f6_2 + f4f5_2 + f9f10_38, + f1f9_2 + f2f8_4 + f3f7_2 + f4f6_4 + f5f5 + f10f10_38, + f1f10_2 + f2f9_2 + f3f8_2 + f4f7_2 + f5f6_2 + } + local carry + carry = h[1] + (1 << 25); h[2] = h[2] + arshift(carry, 26); h[1] = h[1] - (carry & kTop38Bits) + carry = h[5] + (1 << 25); h[6] = h[6] + arshift(carry, 26); h[5] = h[5] - (carry & kTop38Bits) + carry = h[2] + (1 << 24); h[3] = h[3] + arshift(carry, 25); h[2] = h[2] - (carry & kTop39Bits) + carry = h[6] + (1 << 24); h[7] = h[7] + arshift(carry, 25); h[6] = h[6] - (carry & kTop39Bits) + carry = h[3] + (1 << 25); h[4] = h[4] + arshift(carry, 26); h[3] = h[3] - (carry & kTop38Bits) + carry = h[7] + (1 << 25); h[8] = h[8] + arshift(carry, 26); h[7] = h[7] - (carry & kTop38Bits) + carry = h[4] + (1 << 24); h[5] = h[5] + arshift(carry, 25); h[4] = h[4] - (carry & kTop39Bits) + carry = h[8] + (1 << 24); h[9] = h[9] + arshift(carry, 25); h[8] = h[8] - (carry & kTop39Bits) + carry = h[5] + (1 << 25); h[6] = h[6] + arshift(carry, 26); h[5] = h[5] - (carry & kTop38Bits) + carry = h[9] + (1 << 25); h[10] = h[10] + arshift(carry, 26); h[9] = h[9] - (carry & kTop38Bits) + carry = h[10] + (1 << 24); h[1] = h[1] + arshift(carry, 25) * 19; h[10] = h[10] - (carry & kTop39Bits) + carry = h[1] + (1 << 25); h[2] = h[2] + arshift(carry, 26); h[1] = h[1] - (carry & kTop38Bits) + return h +end + +local function fe_invert(z) + local t0 = fe_sq(z) + local t1 = fe_sq(t0) + t1 = fe_sq(t1) + t1 = fe_mul(z, t1) + t0 = fe_mul(t0, t1) + + local t2 = fe_sq(t0) + + t1 = fe_mul(t1, t2) + + t2 = fe_sq(t1) + for _ = 1, 4 do + t2 = fe_sq(t2) + end + + t1 = fe_mul(t2, t1) + t2 = fe_sq(t1) + for _ = 1, 9 do + t2 = fe_sq(t2) + end + t2 = fe_mul(t2, t1) + local t3 = fe_sq(t2) + for _ = 1, 19 do + t3 = fe_sq(t3) + end + t2 = fe_mul(t3, t2) + + for _ = 0, 9 do + t2 = fe_sq(t2) + end + t1 = fe_mul(t2, t1) + t2 = fe_sq(t1) + for _ = 1, 49 do + t2 = fe_sq(t2) + end + t2 = fe_mul(t2, t1) + + t3 = fe_sq(t2) + + for _ = 1, 99 do + t3 = fe_sq(t3); + end + t2 = fe_mul(t3, t2) + t2 = fe_sq(t2) + + for _ = 1, 49 do + t2 = fe_sq(t2) + end + t1 = fe_mul(t2, t1) + t1 = fe_sq(t1) + for _ = 1, 4 do + t1 = fe_sq(t1) + end + + return fe_mul(t1, t0) +end + +local function x25519_scalar_mult_generic(scalar, point) + local x1 = fe_frombytes(point) + local x2 = { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 } + local z2 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } + local x3 = { table.unpack(x1) } + local z3 = { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 } + + local swap = 0 + + local e = { table.unpack(scalar) } + e[1] = e[1] & 248 + e[32] = e[32] & 127 + e[32] = e[32] | 64 + + for pos = 254, 0, -1 do + local b = 1 & (e[pos // 8 + 1] >> (pos & 7)) + swap = swap ~ b + fe_cswap(x2, x3, swap) + fe_cswap(z2, z3, swap) + swap = b + local tmp0 = fe_sub(x3, z3) + + local tmp1 = fe_sub(x2, z2) + fe_addto(x2, z2) + z2 = fe_add(x3, z3) + z3 = fe_mul(tmp0, x2) + + z2 = fe_mul(z2, tmp1) + tmp0 = fe_sq(tmp1) + tmp1 = fe_sq(x2) + x3 = fe_add(z3, z2); + z2 = fe_sub(z3, z2); + x2 = fe_mul(tmp1, tmp0) + fe_subto(tmp1, tmp0); + z2 = fe_sq(z2) + z3 = fe_mul121666(tmp1) + x3 = fe_sq(x3) + fe_addto(tmp0, z3) + z3 = fe_mul(x1, z2) + z2 = fe_mul(tmp1, tmp0) + end + + -- safety or something + for i = 1, 32 do + e[i] = 0 + end + + return fe_tobytes(fe_mul(fe_invert(z2), x2)) +end + +return x25519_scalar_mult_generic diff --git a/run.lua b/run.lua new file mode 100755 index 0000000..da24f3d --- /dev/null +++ b/run.lua @@ -0,0 +1,39 @@ +#!/usr/bin/env lua +local curve25519 = dofile("curve25519.lua") + +local function print_hex(a) + for _, h in pairs(a) do + io.write(string.format("%02x", h)) + end + print() +end + +local base_point = { 9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 } +local client_private = { + 0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08, + 0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,0x10, + 0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18, + 0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,0x20 +} +local server_private = { + 0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8, + 0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf,0xb0, + 0xb1,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8, + 0xb9,0xba,0xbb,0xbc,0xbd,0xbe,0xbf,0xc0 +} + +local client_public = curve25519(client_private, base_point) +local server_public = curve25519(server_private, base_point) + +print("client_public") +print_hex(client_public) +print("server_public") +print_hex(server_public) + +local client_shared = curve25519(client_private, server_public) +local server_shared = curve25519(server_private, client_public) + +print("client_shared") +print_hex(client_shared) +print("server_shared") +print_hex(server_shared) diff --git a/ssh.lua b/ssh.lua new file mode 100644 index 0000000..b5a5c06 --- /dev/null +++ b/ssh.lua @@ -0,0 +1,264 @@ +local program_name = "BlueSSH" +local program_version = "1.0" +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 = {} + local module + setmetatable(this, { + __index = function(_, key) + module = module or require(name) + return module[key] + end, + __call = function(_, ...) + module = module or require(name) + return module(...) + end + }) + return this +end + +local curve25519 = lazy_require("curve25519") + +local function parse_ssh_version(str) + local proto, software, comments = str:match("^SSH%-(%d+%.%d+)%-(%S+)%s?(.*)\r\n$") + if not proto then return nil, "invalid SSH version string" end + return proto, software, comments or nil +end + +local function encode_ssh_version(proto, software, comments) + return "SSH-" .. proto .. "-" .. software .. (comments and (" " .. comments) or "") .. "\r\n" +end + +local random + +if not component.isAvailable("data") or component.data.random == nil then + terminal.write("ssh: WARNING: THIS PROGRAM IS NOT CRYPTOGRAPHICALLY SAFE WITHOUT A TIER 2 DATA CARD, DO NOT CONNECT TO A REAL SERVER WITH THIS\n") + random = function(n) + local parts = {} + for i = 1, n do parts[i] = math.random(0, 255) end + return table.concat(parts) + end +else + random = component.data.random +end + +local SSH_MSG_DISCONNECT = 1 +local SSH_MSG_IGNORE = 2 +local SSH_MSG_UNIMPLEMENTED = 3 +local SSH_MSG_DEBUG = 4 +local SSH_MSG_SERVICE_REQUEST = 5 +local SSH_MSG_SERVICE_ACCEPT = 6 +local SSH_MSG_KEXINIT = 20 +local SSH_MSG_NEWKEYS = 21 + +local function socket() + local this = {} + local _sock + local _host + local _port + + function this.connect(host, port) + _host = host; _port = port + _sock = internet.connect(host, port) + + local ok = false + for _ = 1, 50 do + local err + ok, err = _sock.finishConnect() + if ok == nil then + return nil, err + end + if ok == true then + return true + end + coroutine.yield() + end + return nil, "timed out" + end + + local _read_buf = "" + + function this.read(n) + 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 + end + local to_return = _read_buf:sub(1, n); + _read_buf = _read_buf:sub(n + 1, -1) + return to_return + end + + function this.read_until_crlf(n) + local chunk, err = _sock.read(n) + if not chunk then return nil, err end + _read_buf = _read_buf .. chunk + + local crlf_pos = _read_buf:find("\r\n", 1, true) + if crlf_pos == nil then return nil end + + local to_return = _read_buf:sub(1, crlf_pos + 1) + _read_buf = _read_buf:sub(crlf_pos + 2, -1) + + return to_return + end + + function this.read_uint8() + local x, err = this.read(1) + if x == nil then return nil, err end + return string.unpack(">I1", x) + end + + function this.read_uint32() + local x, err = this.read(4) + if x == nil then return nil, err end + return string.unpack(">I4", x) + end + + function this.read_packet(mac_length) + mac_length = mac_length or 0 + + local packet_length, err = this.read_uint32() + if not packet_length then return nil, err end + + local padding_length, err = this.read_uint8() + if not padding_length then return nil, err end + + local payload, err = this.read(packet_length - padding_length - 1) + if not payload then return nil, err end + + local random_padding, err = this.read(padding_length) + if not random_padding then return nil, err end + + local mac, err = this.read(mac_length) + if not mac then return nil, err end + + return payload + end + + function this.write_packet(payload, block_size) + block_size = block_size or 8 + local min_padding = 4 + + local padding_length = block_size - ((5 + #payload) % block_size) + if padding_length < min_padding then + padding_length = padding_length + block_size + end + + local padding = random(padding_length) + + local packet_length = 1 + #payload + padding_length + local packet = string.pack(">I4", packet_length) + .. string.char(padding_length) + .. payload + .. padding + + return this.write(packet) + end + + local function parse_namelist(payload, pos) + local len = string.unpack(">I4", payload, pos) + local s = payload:sub(pos + 4, pos + 4 + len - 1) + local list = {} + if #s > 0 then + for name in s:gmatch("[^,]+") do + list[#list + 1] = name + end + end + return list, pos + 4 + len + end + + local function encode_namelist(list) + return string.pack(">s4", table.concat(list or {}, ",")) + end + + function this.parse_kexinit(payload) + local packet_type = payload:byte(1) + if packet_type ~= SSH_MSG_KEXINIT then return nil, "unknown server side error" end + + local pos = 2 + local kex = {} + kex.cookie = payload:sub(pos, pos + 15) + pos = pos + 16 + + kex.kex_algorithms, pos = parse_namelist(payload, pos) + kex.server_host_key_algorithms, pos = parse_namelist(payload, pos) + kex.encryption_algorithms_client_to_server, pos = parse_namelist(payload, pos) + kex.encryption_algorithms_server_to_client, pos = parse_namelist(payload, pos) + kex.mac_algorithms_client_to_server, pos = parse_namelist(payload, pos) + kex.mac_algorithms_server_to_client, pos = parse_namelist(payload, pos) + kex.compression_algorithms_client_to_server, pos = parse_namelist(payload, pos) + kex.compression_algorithms_server_to_client, pos = parse_namelist(payload, pos) + kex.languages_client_to_server, pos = parse_namelist(payload, pos) + kex.languages_server_to_client, pos = parse_namelist(payload, pos) + + kex.first_kex_packet_follows = payload:byte(pos) ~= 0 + pos = pos + 1 + + -- next 4 bytes are reserved (uint32 == 0) + return kex + end + + function this.build_kexinit(algs) + return string.char(SSH_MSG_KEXINIT) .. + random(16) .. + encode_namelist(algs.kex_algorithms) .. + encode_namelist(algs.server_host_key_algorithms) .. + encode_namelist(algs.encryption_algorithms_client_to_server) .. + encode_namelist(algs.encryption_algorithms_server_to_client) .. + encode_namelist(algs.mac_algorithms_client_to_server) .. + encode_namelist(algs.mac_algorithms_server_to_client) .. + encode_namelist(algs.compression_algorithms_client_to_server) .. + encode_namelist(algs.compression_algorithms_server_to_client) .. + encode_namelist(algs.languages_client_to_server) .. + encode_namelist(algs.languages_server_to_client) .. + '\0' .. -- first_kex_packet_follows + '\0\0\0\0' -- reserved + end + + function this.write(x) + local written = 0 + while written < #x do + local bytes, err = _sock.write(x:sub(written + 1, -1)) + if not bytes then return nil, err end + written = written + bytes + end + return written + end + + return this +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)) +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 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 = {}, + 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