╭ changed ─────────────────╮ │src/serve_ssh_protocol.zig│ ╰──────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────╮ │ 3 //! handles, per connection: version exchange, KEXINIT, curve25519 key │ │ 4 //! exchange, NEWKEYS, key derivation, chacha20-poly1305 packet codec, │ │ 5 //! strict kex (Terrapin mitigation), client-initiated rekeying, │ │ 6 -//! ssh-userauth service request, publickey authentication (ed25519 only; │ │ 7 -//! captures the SHA256 fingerprint of the verified key), channel-layer │ │ 6 +//! ssh-userauth service request, publickey authentication (ed25519 and │ │ 7 +//! rsa-sha2; captures the SHA256 fingerprint of the verified key), channel-layer│ │ 8 //! requests (pty-req, env, window-change, shell, exec), CHANNEL_DATA │ │ 9 //! flow with sender/receiver windows, and clean teardown │ │10 //! (exit-status / EOF / CLOSE). │ ╰─────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────╮ │26 //! cipher: chacha20-poly1305@openssh.com (RFC 4253 + openssh extension)│ │27 //! MAC: none (implicit in AEAD) │ │28 //! compress: none │ │29 -//! user auth: publickey + ssh-ed25519 only (RSA/ECDSA deferred) │ │29 +//! user auth: publickey + ssh-ed25519, rsa-sha2-512, rsa-sha2-256 │ │30 │ │31 const std = @import("std"); │ │32 const builtin = @import("builtin"); │ ╰─────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────╮ │33 const Ed25519 = std.crypto.sign.Ed25519; │ │34 const X25519 = std.crypto.dh.X25519; │ │35 const Sha256 = std.crypto.hash.sha2.Sha256; │ │36 +const Sha512 = std.crypto.hash.sha2.Sha512; │ │37 +const rsa = std.crypto.Certificate.rsa; │ │38 const ChaCha20 = std.crypto.stream.chacha.ChaCha20With64BitNonce;│ │39 const Poly1305 = std.crypto.onetimeauth.Poly1305; │ │40 │ ╰─────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────╮ │58 pub const SSH_MSG_DEBUG: u8 = 4; │ │59 pub const SSH_MSG_SERVICE_REQUEST: u8 = 5;│ │60 pub const SSH_MSG_SERVICE_ACCEPT: u8 = 6; │ │61 +pub const SSH_MSG_EXT_INFO: u8 = 7; │ │62 pub const SSH_MSG_KEXINIT: u8 = 20; │ │63 pub const SSH_MSG_NEWKEYS: u8 = 21; │ │64 pub const SSH_MSG_KEX_ECDH_INIT: u8 = 30; │ ╰──────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1014 // algorithm because the matcher only picks from our_kex_algos. │ │1015 pub const kex_strict_server = "kex-strict-s-v00@openssh.com"; │ │1016 pub const kex_strict_client = "kex-strict-c-v00@openssh.com"; │ │1017 +// the client asks for EXT_INFO after the initial NEWKEYS (RFC 8308) │ │1018 +pub const ext_info_client = "ext-info-c"; │ │1019 │ │1020 const MLKem768 = std.crypto.kem.ml_kem.MLKem768; │ │1021 const hybrid_client_blob_len = MLKem768.PublicKey.encoded_length + X25519.public_length; // 1216│ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1022 const hybrid_server_blob_len = MLKem768.ciphertext_length + X25519.public_length; // 1120 │ │1023 pub const our_host_key_algos = [_][]const u8{"ssh-ed25519"}; │ │1024 +pub const our_userauth_algos = [_][]const u8{ "ssh-ed25519", "rsa-sha2-512", "rsa-sha2-256" };│ │1025 pub const our_ciphers = [_][]const u8{"chacha20-poly1305@openssh.com"}; │ │1026 const our_macs = [_][]const u8{}; // none — implicit in the AEAD cipher │ │1027 const our_compression = [_][]const u8{"none"}; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────╮ │1128 // seqnos reset to 0 after every NEWKEYS. │ │1129 const strict = firstCommonName(parsed.kex_algos, &.{kex_strict_client}) != null;│ │1130 if (strict and seqs.read != 1) return error.StrictKexViolation; │ │1131 + const ext_info = firstCommonName(parsed.kex_algos, &.{ext_info_client}) != null;│ │1132 │ │1133 var keys: SessionKeys = undefined; │ │1134 defer std.crypto.secureZero(u8, std.mem.asBytes(&keys)); │ ╰──────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────╮ │1151 // post-KEX everything is encrypted with chacha20-poly1305@openssh.com.│ │1152 // each cipher continues its own direction's count; the read side │ │1153 // includes any skipped IGNORE/DEBUG packets. │ │1147 - return .{ │ │1154 + var conn: Conn = .{ │ │1155 .io = io, │ │1156 .allocator = allocator, │ │1157 .reader = reader, │ ╰─────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────╮ │1166 }, │ │1167 .idle = idle, │ │1168 }; │ │1169 + if (ext_info) try sendExtInfo(&conn); │ │1170 + return conn; │ │1171 +} │ │1172 + │ │1173 +// advertise server-sig-algs so clients will offer rsa-sha2 keys (RFC 8308 §3.1)│ │1174 +fn sendExtInfo(conn: *Conn) !void { │ │1175 + var buf: std.ArrayList(u8) = .empty; │ │1176 + defer buf.deinit(conn.allocator); │ │1177 + try buf.append(conn.allocator, SSH_MSG_EXT_INFO); │ │1178 + try writeU32(&buf, conn.allocator, 1); // one extension │ │1179 + try writeStringField(&buf, conn.allocator, "server-sig-algs"); │ │1180 + try writeNameList(&buf, conn.allocator, &our_userauth_algos); │ │1181 + try conn.writePacket(&.{buf.items}); │ │1182 } │ │1183 │ │1184 /// everything needed to service a client-initiated rekey mid-session. │ ╰──────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │1682 try conn.writePacket(&.{accept.items}); │ │1683 } │ │1684 │ │1665 - // step 2: USERAUTH loop until SUCCESS. accept any ed25519 key whose │ │1685 + // step 2: USERAUTH loop until SUCCESS. accept any supported key whose│ │1686 // signature verifies against the offered pubkey. │ │1687 var auth_attempts: u32 = 0; │ │1688 while (true) { │ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯