╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ─────────────────╮ │src/serve_ssh_protocol.zig│ ╰──────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────╮ │610 try writer.flush(); │ │611 } │ │612 │ │617 -fn readPlainPacket(allocator: std.mem.Allocator, reader: *std.Io.Reader) ![]u8 { │ │613 +pub fn readPlainPacket(allocator: std.mem.Allocator, reader: *std.Io.Reader) ![]u8 { │ │614 const packet_len = try reader.takeInt(u32, .big); │ │615 if (packet_len < 8 or packet_len > max_packet_len) return error.InvalidPacketLength;│ │616 const padding_len = try reader.takeByte(); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │627 // SSH field encoders / decoders (RFC 4251 §5) │ │628 // --------------------------------------------------------------------------- │ │629 │ │634 -fn writeStringField(buf: *std.ArrayList(u8), allocator: std.mem.Allocator, bytes: []const u8) !void { │ │630 +pub fn writeStringField(buf: *std.ArrayList(u8), allocator: std.mem.Allocator, bytes: []const u8) !void {│ │631 var len_bytes: [4]u8 = undefined; │ │632 std.mem.writeInt(u32, &len_bytes, @intCast(bytes.len), .big); │ │633 try buf.appendSlice(allocator, &len_bytes); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │634 try buf.appendSlice(allocator, bytes); │ │635 } │ │636 │ │641 -fn writeNameList(buf: *std.ArrayList(u8), allocator: std.mem.Allocator, names: []const []const u8) !void { │ │637 +pub fn writeNameList(buf: *std.ArrayList(u8), allocator: std.mem.Allocator, names: []const []const u8) !void {│ │638 var total: usize = 0; │ │639 for (names, 0..) |name, i| { │ │640 if (i > 0) total += 1; // comma separator │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │653 /// our use (X25519 shared secret) the value is always non-negative, so we │ │654 /// strip leading zero bytes, then prepend a single 0x00 if the high bit of │ │655 /// the most-significant byte is set (to keep it parsed as positive). │ │660 -fn writeMpint(buf: *std.ArrayList(u8), allocator: std.mem.Allocator, bytes: []const u8) !void { │ │656 +pub fn writeMpint(buf: *std.ArrayList(u8), allocator: std.mem.Allocator, bytes: []const u8) !void {│ │657 var start: usize = 0; │ │658 while (start < bytes.len and bytes[start] == 0) start += 1; │ │659 const trimmed = bytes[start..]; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────╮ │670 try buf.appendSlice(allocator, trimmed); │ │671 } │ │672 │ │677 -fn takeStringField(allocator: std.mem.Allocator, reader: *std.Io.Reader, max_len: u32) ![]u8 { │ │673 +pub fn takeStringField(allocator: std.mem.Allocator, reader: *std.Io.Reader, max_len: u32) ![]u8 {│ │674 const len = try reader.takeInt(u32, .big); │ │675 if (len > max_len) return error.FieldTooLarge; │ │676 return try reader.readAlloc(allocator, len); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────╮ │690 // KEXINIT (RFC 4253 §7.1) │ │691 // --------------------------------------------------------------------------- │ │692 │ │697 -const our_kex_algos = [_][]const u8{ "curve25519-sha256", "curve25519-sha256@libssh.org" }; │ │698 -const our_host_key_algos = [_][]const u8{"ssh-ed25519"}; │ │699 -const our_ciphers = [_][]const u8{"chacha20-poly1305@openssh.com"}; │ │693 +pub const our_kex_algos = [_][]const u8{ "curve25519-sha256", "curve25519-sha256@libssh.org" };│ │694 +pub const our_host_key_algos = [_][]const u8{"ssh-ed25519"}; │ │695 +pub const our_ciphers = [_][]const u8{"chacha20-poly1305@openssh.com"}; │ │696 const our_macs = [_][]const u8{}; // none — implicit in the AEAD cipher │ │697 const our_compression = [_][]const u8{"none"}; │ │698 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────╮ │703 -fn buildServerKexInit(io: std.Io, allocator: std.mem.Allocator) ![]u8 {│ │699 +/// Build a KEXINIT payload with the given algorithm name-lists │ │700 +pub fn buildKexInit( │ │701 + io: std.Io, │ │702 + allocator: std.mem.Allocator, │ │703 + kex_algos: []const []const u8, │ │704 + host_key_algos: []const []const u8, │ │705 + ciphers: []const []const u8, │ │706 +) ![]u8 { │ │707 var buf: std.ArrayList(u8) = .empty; │ │708 errdefer buf.deinit(allocator); │ │709 │ ╰────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────╮ │713 io.random(&cookie); │ │714 try buf.appendSlice(allocator, &cookie); │ │715 │ │713 - try writeNameList(&buf, allocator, &our_kex_algos); │ │714 - try writeNameList(&buf, allocator, &our_host_key_algos); │ │715 - try writeNameList(&buf, allocator, &our_ciphers); // c->s│ │716 - try writeNameList(&buf, allocator, &our_ciphers); // s->c│ │716 + try writeNameList(&buf, allocator, kex_algos); │ │717 + try writeNameList(&buf, allocator, host_key_algos); │ │718 + try writeNameList(&buf, allocator, ciphers); // c->s │ │719 + try writeNameList(&buf, allocator, ciphers); // s->c │ │720 try writeNameList(&buf, allocator, &our_macs); │ │721 try writeNameList(&buf, allocator, &our_macs); │ │722 try writeNameList(&buf, allocator, &our_compression); │ ╰──────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────╮ │729 return try buf.toOwnedSlice(allocator); │ │730 } │ │731 │ │732 +fn buildServerKexInit(io: std.Io, allocator: std.mem.Allocator) ![]u8 { │ │733 + return buildKexInit(io, allocator, &our_kex_algos, &our_host_key_algos, &our_ciphers);│ │734 +} │ │735 + │ │736 const ParsedKexInit = struct { │ │737 kex_algos: []u8, │ │738 host_key_algos: []u8, │ ╰───────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │886 // exchange hash (RFC 4253 §8, RFC 5656 §4) │ │887 // ---------------------------------------------------------------------------│ │888 │ │882 -fn computeExchangeHash( │ │889 +pub fn computeExchangeHash( │ │890 allocator: std.mem.Allocator, │ │891 client_version: []const u8, │ │892 server_version_str: []const u8, │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯