╭ changed ─────────────────╮ │src/serve_ssh_protocol.zig│ ╰──────────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────╮ │← previous│ ╰──────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1323 ) ![Sha256.digest_length]u8 { │ │1324 var buf: std.ArrayList(u8) = .empty; │ │1325 defer buf.deinit(allocator); │ │1326 + defer std.crypto.secureZero(u8, buf.items); │ │1327 + // reserved up front so a reallocation can't leave K behind │ │1328 + try buf.ensureTotalCapacityPrecise(allocator, 8 * 4 + 1 + client_version.len + server_version_str.len +│ │1329 + client_kex_init.len + server_kex_init.len + host_key_blob.len + client_ephemeral.len + │ │1330 + server_ephemeral.len + shared_secret.len); │ │1331 │ │1332 try writeStringField(&buf, allocator, client_version); │ │1333 try writeStringField(&buf, allocator, server_version_str); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────╮ │1343 } │ │1344 │ │1345 var hasher = Sha256.init(.{}); │ │1346 + defer std.crypto.secureZero(u8, std.mem.asBytes(&hasher));│ │1347 hasher.update(buf.items); │ │1348 return hasher.finalResult(); │ │1349 } │ ╰────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1372 // pre-build the K || H prefix once; reused for every derivation │ │1373 var prefix: std.ArrayList(u8) = .empty; │ │1374 defer prefix.deinit(allocator); │ │1375 + defer std.crypto.secureZero(u8, prefix.items); │ │1376 + // reserved up front so a reallocation can't leave K behind │ │1377 + try prefix.ensureTotalCapacityPrecise(allocator, 4 + 1 + shared_secret.len + exchange_hash.len);│ │1378 if (k_is_string) { │ │1379 try writeStringField(&prefix, allocator, shared_secret); │ │1380 } else { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────╮ │1382 } │ │1383 try prefix.appendSlice(allocator, exchange_hash); │ │1384 │ │1366 - try deriveKey(allocator, prefix.items, 'C', session_id, &out.cs_enc);│ │1367 - try deriveKey(allocator, prefix.items, 'D', session_id, &out.sc_enc);│ │1385 + deriveKey(prefix.items, 'C', session_id, &out.cs_enc); │ │1386 + deriveKey(prefix.items, 'D', session_id, &out.sc_enc); │ │1387 } │ │1388 │ │1389 fn deriveKey( │ ╰───────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────╮ │1371 - allocator: std.mem.Allocator, │ │1390 prefix: []const u8, // K || H, already encoded│ │1391 letter: u8, │ │1392 session_id: []const u8, │ ╰────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────╮ │1375 - out: []u8, │ │1376 -) !void { │ │1377 - // first block: HASH(K || H || letter || session_id) │ │1378 - var first_block: [Sha256.digest_length]u8 = undefined; │ │1379 - { │ │1380 - var hasher = Sha256.init(.{}); │ │1381 - hasher.update(prefix); │ │1382 - hasher.update(&[_]u8{letter}); │ │1383 - hasher.update(session_id); │ │1384 - first_block = hasher.finalResult(); │ │1385 - } │ │1386 - const first_copy = @min(out.len, first_block.len); │ │1387 - @memcpy(out[0..first_copy], first_block[0..first_copy]); │ │1388 - │ │1389 - // chain additional blocks if requested length exceeds one hash: │ │1390 - // K_{n+1} = HASH(K || H || K_1 || K_2 || … || K_n) │ │1391 - var chained: std.ArrayList(u8) = .empty; │ │1392 - defer chained.deinit(allocator); │ │1393 - try chained.appendSlice(allocator, first_block[0..]); │ │1393 + out: *[2 * Sha256.digest_length]u8, │ │1394 +) void { │ │1395 + // a finished context still holds its digest and last input block │ │1396 + // K_1 = HASH(K || H || letter || session_id) │ │1397 + var first = Sha256.init(.{}); │ │1398 + defer std.crypto.secureZero(u8, std.mem.asBytes(&first)); │ │1399 + first.update(prefix); │ │1400 + first.update(&.{letter}); │ │1401 + first.update(session_id); │ │1402 + first.final(out[0..Sha256.digest_length]); │ │1403 │ │1395 - var produced: usize = first_copy; │ │1396 - while (produced < out.len) { │ │1397 - var hasher = Sha256.init(.{}); │ │1398 - hasher.update(prefix); │ │1399 - hasher.update(chained.items); │ │1400 - const block = hasher.finalResult(); │ │1401 - const copy_len = @min(out.len - produced, block.len); │ │1402 - @memcpy(out[produced .. produced + copy_len], block[0..copy_len]); │ │1403 - produced += copy_len; │ │1404 - try chained.appendSlice(allocator, block[0..]); │ │1405 - } │ │1404 + // K_2 = HASH(K || H || K_1) │ │1405 + var second = Sha256.init(.{}); │ │1406 + defer std.crypto.secureZero(u8, std.mem.asBytes(&second)); │ │1407 + second.update(prefix); │ │1408 + second.update(out[0..Sha256.digest_length]); │ │1409 + second.final(out[Sha256.digest_length..]); │ │1410 } │ │1411 │ │1412 // ---------------------------------------------------------------------------│ ╰────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────╮ │1429 pub const Cipher = struct { │ │1430 main_key: [32]u8, // K_2 │ │1431 header_key: [32]u8, // K_1 │ │1432 + // wider than the 32-bit wire seqno, so a peer that never rekeys fails the│ │1433 + // mac at 2^32 packets instead of wrapping into nonce reuse │ │1434 seq: u64, │ │1435 │ │1436 pub fn init(key_material: *const [64]u8, initial_seq: u64) Cipher { │ ╰────────────────────────────────────────────────────────────────────────────────────╯