╭ changed ─────────────────╮ │src/serve_ssh_protocol.zig│ ╰──────────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────╮ │← previous│ ╰──────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────╮ │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, │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────╮ │921 // chacha20-poly1305@openssh.com wants 64 bytes per direction (32-byte main key│ │922 // + 32-byte header key); SHA256 produces 32, so each encrypt key is two hash │ │923 // blocks chained per RFC 4253. │ │917 -const SessionKeys = struct { │ │924 +pub const SessionKeys = struct { │ │925 cs_iv: [16]u8, │ │926 sc_iv: [16]u8, │ │927 cs_enc: [64]u8, │ ╰────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────╮ │928 sc_enc: [64]u8, │ │929 }; │ │930 │ │924 -fn deriveSessionKeys( │ │931 +pub fn deriveSessionKeys( │ │932 allocator: std.mem.Allocator,│ │933 shared_secret: []const u8, │ │934 exchange_hash: []const u8, │ ╰──────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────╮ │1002 // padding) — the 4-byte length field is excluded from alignment. body must│ │1003 // be a multiple of 8 bytes; minimum padding 4 bytes. │ │1004 │ │ 998 -const Cipher = struct { │ │1005 +pub const Cipher = struct { │ │1006 main_key: [32]u8, // K_2 │ │1007 header_key: [32]u8, // K_1 │ │1008 seq: u64, │ ╰─────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────╮ │1009 │ │1003 - fn init(key_material: *const [64]u8, initial_seq: u64) Cipher { │ │1010 + pub fn init(key_material: *const [64]u8, initial_seq: u64) Cipher {│ │1011 return .{ │ │1012 .main_key = key_material[0..32].*, │ │1013 .header_key = key_material[32..64].*, │ ╰─────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯