╭ changed ─────────────────╮ │src/serve_ssh_protocol.zig│ ╰──────────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────╮ │672 const client_version = try exchangeVersions(allocator, reader, writer); │ │673 defer allocator.free(client_version); │ │674 │ │675 - const kex = try runKex(io, allocator, reader, writer, host_key, client_version);│ │675 + var kex = try runKex(io, allocator, reader, writer, host_key, client_version); │ │676 + defer std.crypto.secureZero(u8, std.mem.asBytes(&kex)); │ │677 │ │678 // post-KEX everything is encrypted with chacha20-poly1305@openssh.com │ │679 var conn = Conn{ │ ╰─────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────╮ │691 }, │ │692 .idle = idle, │ │693 }; │ │694 + defer std.crypto.secureZero(u8, std.mem.asBytes(&conn.cs_cipher));│ │695 + defer std.crypto.secureZero(u8, std.mem.asBytes(&conn.sc_cipher));│ │696 │ │697 const fingerprint = runAuth(&conn) catch |err| { │ │698 disconnectOnError(&conn, err); │ ╰───────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────╮ │1074 const strict = nameListContainsAny(parsed.kex_algos, &.{kex_strict_client});│ │1075 if (strict and seqs.read != 1) return error.StrictKexViolation; │ │1076 │ │1074 - const negotiated = try exchangeKeys( │ │1077 + var negotiated = try exchangeKeys( │ │1078 io, │ │1079 allocator, │ │1080 reader, │ ╰──────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────╮ │1088 &parsed, │ │1089 null, │ │1090 ); │ │1091 + defer std.crypto.secureZero(u8, std.mem.asBytes(&negotiated.keys));│ │1092 │ │1093 return .{ │ │1094 .cs_key = negotiated.keys.cs_enc, │ ╰─────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │1124 defer conn.allocator.free(server_kex_init); │ │1125 try conn.sc_cipher.writePacket(conn.io, conn.writer, server_kex_init);│ │1126 │ │1123 - const negotiated = try exchangeKeys( │ │1127 + var negotiated = try exchangeKeys( │ │1128 conn.io, │ │1129 conn.allocator, │ │1130 conn.reader, │ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────╮ │1138 &parsed, │ │1139 &conn.rekey.session_id, │ │1140 ); │ │1141 + defer std.crypto.secureZero(u8, std.mem.asBytes(&negotiated.keys));│ │1142 │ │1143 // seqnos continue across a rekey unless strict kex was negotiated,│ │1144 // which resets them after every NEWKEYS │ ╰─────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────╮ │1218 // ECDH: server_blob = X25519 server pub (32B); K = X25519(s, c_pub). │ │1219 // hybrid: server_blob = MLKEM ciphertext (1088B) || X25519 server pub (32B); │ │1220 // K = SHA-256(K_MLKEM || K_X25519). │ │1216 - var server_blob: []u8 = undefined; │ │1217 - var k: []u8 = undefined; │ │1218 - { │ │1221 + var server_blob_buf: [hybrid_server_blob_len]u8 = undefined; │ │1222 + var k: [Sha256.digest_length]u8 = undefined; │ │1223 + defer std.crypto.secureZero(u8, &k); │ │1224 + const server_blob: []const u8 = blk: { │ │1225 const x25519_offset: usize = if (hybrid) MLKem768.PublicKey.encoded_length else 0;│ │1226 var x25519_client_pub: [X25519.public_length]u8 = undefined; │ │1227 @memcpy(&x25519_client_pub, client_blob[x25519_offset..]); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────╮ │1222 - const server_kp = X25519.KeyPair.generate(io); │ │1223 - const x25519_shared = try X25519.scalarmult(server_kp.secret_key, x25519_client_pub);│ │1228 + var server_kp = X25519.KeyPair.generate(io); │ │1229 + defer std.crypto.secureZero(u8, &server_kp.secret_key); │ │1230 + var x25519_shared = try X25519.scalarmult(server_kp.secret_key, x25519_client_pub); │ │1231 + defer std.crypto.secureZero(u8, &x25519_shared); │ │1232 + │ │1233 + if (!hybrid) { │ │1234 + server_blob_buf[0..X25519.public_length].* = server_kp.public_key; │ │1235 + k = x25519_shared; │ │1236 + break :blk server_blob_buf[0..X25519.public_length]; │ │1237 + } │ │1238 │ │1225 - if (hybrid) { │ │1226 - var pq_pub_bytes: [MLKem768.PublicKey.encoded_length]u8 = undefined; │ │1227 - @memcpy(&pq_pub_bytes, client_blob[0..MLKem768.PublicKey.encoded_length]); │ │1228 - const pq_pub = try MLKem768.PublicKey.fromBytes(&pq_pub_bytes); │ │1229 - const encap = pq_pub.encaps(io); │ │1239 + var pq_pub_bytes: [MLKem768.PublicKey.encoded_length]u8 = undefined; │ │1240 + @memcpy(&pq_pub_bytes, client_blob[0..MLKem768.PublicKey.encoded_length]); │ │1241 + const pq_pub = try MLKem768.PublicKey.fromBytes(&pq_pub_bytes); │ │1242 + var encap = pq_pub.encaps(io); │ │1243 + defer std.crypto.secureZero(u8, &encap.shared_secret); │ │1244 │ │1231 - server_blob = try allocator.alloc(u8, hybrid_server_blob_len); │ │1232 - @memcpy(server_blob[0..MLKem768.ciphertext_length], &encap.ciphertext); │ │1233 - @memcpy(server_blob[MLKem768.ciphertext_length..], &server_kp.public_key); │ │1245 + server_blob_buf[0..MLKem768.ciphertext_length].* = encap.ciphertext; │ │1246 + server_blob_buf[MLKem768.ciphertext_length..].* = server_kp.public_key; │ │1247 │ │1235 - var h = Sha256.init(.{}); │ │1236 - h.update(&encap.shared_secret); │ │1237 - h.update(&x25519_shared); │ │1238 - k = try allocator.dupe(u8, &h.finalResult()); │ │1239 - } else { │ │1240 - server_blob = try allocator.dupe(u8, &server_kp.public_key); │ │1241 - k = try allocator.dupe(u8, &x25519_shared); │ │1242 - } │ │1243 - } │ │1244 - defer allocator.free(server_blob); │ │1245 - defer allocator.free(k); │ │1248 + var h = Sha256.init(.{}); │ │1249 + defer std.crypto.secureZero(u8, std.mem.asBytes(&h)); │ │1250 + h.update(&encap.shared_secret); │ │1251 + h.update(&x25519_shared); │ │1252 + h.final(&k); │ │1253 + break :blk &server_blob_buf; │ │1254 + }; │ │1255 │ │1256 // build host key blob (K_S) │ │1257 var host_key_blob: std.ArrayList(u8) = .empty; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────╮ │1268 host_key_blob.items,│ │1269 client_blob, │ │1270 server_blob, │ │1262 - k, │ │1271 + &k, │ │1272 hybrid, │ │1273 ); │ │1274 │ ╰──────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1297 // derive session keys per RFC 4253 §7.2. only the encrypt keys are │ │1298 // needed for chacha20-poly1305 (no separate MAC/IV). │ │1299 var keys: SessionKeys = undefined; │ │1291 - try deriveSessionKeys(allocator, k, &exchange_hash, if (session_id) |sid| sid else &exchange_hash, &keys, hybrid); │ │1300 + defer std.crypto.secureZero(u8, std.mem.asBytes(&keys)); │ │1301 + try deriveSessionKeys(allocator, &k, &exchange_hash, if (session_id) |sid| sid else &exchange_hash, &keys, hybrid);│ │1302 │ │1303 return .{ .keys = keys, .exchange_hash = exchange_hash }; │ │1304 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯