33 {
34 if (!ctx || ctx->state != CRYPTO_HANDSHAKE_INIT) {
35 return SET_ERRNO(ERROR_INVALID_STATE, "Invalid state: ctx=%p, state=%d", (void *)ctx, ctx ? (int)ctx->state : -1);
36 }
37 if (!transport) {
38 return SET_ERRNO(ERROR_INVALID_PARAM, "transport is NULL");
39 }
40
41
42 int result;
43
44
45 if (packet_type != PACKET_TYPE_CRYPTO_KEY_EXCHANGE_INIT) {
46 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Expected KEY_EXCHANGE_INIT, got packet type %d", packet_type);
47 }
48
49 log_debug("CLIENT_KEY_EXCHANGE: Received packet with payload_len=%zu, kex_size=%u, auth_size=%u, sig_size=%u",
50 payload_len, ctx->crypto_ctx.public_key_size, ctx->crypto_ctx.auth_public_key_size,
51 ctx->crypto_ctx.signature_size);
52
53
54
55 size_t expected_auth_size =
56 ctx->crypto_ctx.public_key_size + ctx->crypto_ctx.auth_public_key_size + ctx->crypto_ctx.signature_size;
57
58 uint8_t *server_ephemeral_key;
59
60 size_t key_size = sizeof(ctx->crypto_ctx.public_key);
61 server_ephemeral_key = SAFE_MALLOC(key_size, uint8_t *);
62 if (!server_ephemeral_key) {
63 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate memory for server ephemeral key");
64 }
65 uint8_t *server_identity_key;
66 server_identity_key = SAFE_MALLOC(ctx->crypto_ctx.auth_public_key_size, uint8_t *);
67 uint8_t *server_signature;
68 server_signature = SAFE_MALLOC(ctx->crypto_ctx.signature_size, uint8_t *);
69
70 if (!server_identity_key || !server_signature) {
71 SAFE_FREE(server_ephemeral_key);
72 if (server_identity_key)
73 SAFE_FREE(server_identity_key);
74 if (server_signature)
75 SAFE_FREE(server_signature);
76 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate memory for server identity key or signature");
77 }
78
79
80 asciichat_error_t validation_result =
82 if (validation_result != ASCIICHAT_OK) {
83 if (payload) {
85 }
86 SAFE_FREE(server_ephemeral_key);
87 SAFE_FREE(server_identity_key);
88 SAFE_FREE(server_signature);
89 return validation_result;
90 }
91
92
93
94 if (ctx->crypto_ctx.auth_public_key_size > 0 && payload_len == expected_auth_size) {
95
96
97 log_debug("Received authenticated KEY_EXCHANGE_INIT (%zu bytes)", expected_auth_size);
98 memcpy(server_ephemeral_key, payload, ctx->crypto_ctx.public_key_size);
99 memcpy(server_identity_key, payload + ctx->crypto_ctx.public_key_size, ctx->crypto_ctx.auth_public_key_size);
100 memcpy(server_signature, payload + ctx->crypto_ctx.public_key_size + ctx->crypto_ctx.auth_public_key_size,
101 ctx->crypto_ctx.signature_size);
102
103
104 ctx->server_uses_client_auth = true;
105
106
107 char hex_id[HEX_STRING_SIZE_32];
108 for (int i = 0; i < ED25519_PUBLIC_KEY_SIZE; i++) {
109 safe_snprintf(hex_id + i * 2, 3,
"%02x", server_identity_key[i]);
110 }
111 hex_id[HEX_STRING_SIZE_32 - 1] = '\0';
112 log_debug("Received identity key: %s", hex_id);
113
114
115 char hex_eph[HEX_STRING_SIZE_32];
116 for (int i = 0; i < ED25519_PUBLIC_KEY_SIZE; i++) {
117 safe_snprintf(hex_eph + i * 2, 3,
"%02x", server_ephemeral_key[i]);
118 }
119 hex_eph[HEX_STRING_SIZE_32 - 1] = '\0';
120 log_debug("Received ephemeral key: %s", hex_eph);
121
122 char hex_sig[HEX_STRING_SIZE_64];
123 for (int i = 0; i < ED25519_SIGNATURE_SIZE; i++) {
124 safe_snprintf(hex_sig + i * 2, 3,
"%02x", server_signature[i]);
125 }
126 hex_sig[HEX_STRING_SIZE_64 - 1] = '\0';
127 log_debug("Received signature: %s", hex_sig);
128
129
130 log_debug("Verifying server's signature over ephemeral key (already logged above)");
131
132
133
134 if (!ctx->verify_server_key) {
135 log_info("Skipping server signature verification (no --server-key specified)");
136 log_warn("Connection is encrypted but server identity is NOT verified (vulnerable to MITM)");
137 } else {
138
139 const char *gpg_key_id = NULL;
140 if (ctx->expected_server_key[0] != '\0' && strncmp(ctx->expected_server_key, "gpg:", 4) == 0) {
141 const char *key_id_start = ctx->expected_server_key + 4;
142 size_t key_id_len = strlen(key_id_start);
143
144 if (key_id_len == 8 || key_id_len == 16 || key_id_len == 40) {
145 gpg_key_id = key_id_start;
146 log_debug("Using GPG key ID from --server-key for verification: %s", gpg_key_id);
147 }
148 }
149
151 server_signature, gpg_key_id) != 0) {
152 if (payload) {
154 }
155 SAFE_FREE(server_ephemeral_key);
156 SAFE_FREE(server_identity_key);
157 SAFE_FREE(server_signature);
158 return SET_ERRNO(ERROR_CRYPTO, "Server signature verification FAILED - rejecting connection. "
159 "This indicates: Server's identity key does not "
160 "match its ephemeral key, Potential man-in-the-middle attack, "
161 "Corrupted or malicious server");
162 }
163 log_debug("Server signature verified successfully");
164 }
165
166
167 if (ctx->verify_server_key && strlen(ctx->expected_server_key) > 0) {
168
169 public_key_t expected_keys[MAX_CLIENTS];
170 size_t num_expected_keys = 0;
171 if (
parse_public_keys(ctx->expected_server_key, expected_keys, &num_expected_keys, MAX_CLIENTS) != 0 ||
172 num_expected_keys == 0) {
173 if (payload) {
175 }
176 SAFE_FREE(server_ephemeral_key);
177 SAFE_FREE(server_identity_key);
178 SAFE_FREE(server_signature);
179 return SET_ERRNO(ERROR_CONFIG,
180 "Failed to parse expected server key: %s. Check that "
181 "--server-key value is valid (ssh-ed25519 "
182 "format, github:username, or hex)",
183 ctx->expected_server_key);
184 }
185
186
187
188 bool key_matched = false;
189 for (size_t i = 0; i < num_expected_keys; i++) {
190 if (sodium_memcmp(server_identity_key, expected_keys[i].key, ED25519_PUBLIC_KEY_SIZE) == 0) {
191 key_matched = true;
192 log_debug("Server identity key matched expected key %zu/%zu", i + 1, num_expected_keys);
193 break;
194 }
195 }
196
197 if (!key_matched) {
198 if (payload) {
200 }
201 SAFE_FREE(server_ephemeral_key);
202 SAFE_FREE(server_identity_key);
203 SAFE_FREE(server_signature);
204 return SET_ERRNO(ERROR_CRYPTO,
205 "Server identity key mismatch - potential MITM attack! "
206 "Expected key(s) from: %s (checked %zu keys), Server presented a different key "
207 "than specified with --server-key, DO NOT CONNECT to this "
208 "server - likely man-in-the-middle attack!",
209 ctx->expected_server_key, num_expected_keys);
210 }
211 log_info("Server identity key verified against --server-key (%zu key(s) checked)", num_expected_keys);
212 }
213
214
215
216
217
218 if (ctx->server_ip[0] == '\0') {
219 log_debug("Server IP not set - skipping known_hosts verification (non-TCP transport)");
220 } else {
221 log_debug("Server IP already set: %s", ctx->server_ip);
222 }
223
224
225
226 bool skip_known_hosts = false;
227 const char *env_skip =
platform_getenv(
"ASCII_CHAT_INSECURE_NO_HOST_IDENTITY_CHECK");
228 if (env_skip && strcmp(env_skip, STR_ONE) == 0) {
229 log_warn(
230 "Skipping known_hosts checking for authenticated connection (ASCII_CHAT_INSECURE_NO_HOST_IDENTITY_CHECK=1)");
231 skip_known_hosts = true;
232 }
233#ifndef NDEBUG
234
236 log_warn("Skipping known_hosts checking (CLAUDECODE set in debug build)");
237 skip_known_hosts = true;
238 }
239#endif
240
241 if (!skip_known_hosts && ctx->server_ip[0] != '\0' && ctx->server_port > 0) {
242 asciichat_error_t known_host_result =
check_known_host(ctx->server_ip, ctx->server_port, server_identity_key);
243 if (known_host_result == ERROR_CRYPTO_VERIFICATION) {
244
245 log_error("SECURITY: Server key does NOT match known_hosts entry!\n"
246 "This indicates a possible man-in-the-middle attack!");
247 uint8_t stored_key[ZERO_KEY_SIZE] = {0};
248
250
251 if (payload) {
253 }
254 SAFE_FREE(server_ephemeral_key);
255 SAFE_FREE(server_identity_key);
256 SAFE_FREE(server_signature);
257 return SET_ERRNO(ERROR_CRYPTO_VERIFICATION,
258 "SECURITY: Connection aborted - server key mismatch (possible MITM attack)");
259 }
260
261 log_warn("SECURITY WARNING: User accepted MITM risk - continuing with connection");
262 } else if (known_host_result == ASCIICHAT_OK) {
263
265
266 if (payload) {
268 }
269 SAFE_FREE(server_ephemeral_key);
270 SAFE_FREE(server_identity_key);
271 SAFE_FREE(server_signature);
272 return SET_ERRNO(ERROR_CRYPTO, "User declined to verify unknown host");
273 }
274
275
276 if (
add_known_host(ctx->server_ip, ctx->server_port, server_identity_key) != ASCIICHAT_OK) {
277 if (payload) {
279 }
280 SAFE_FREE(server_ephemeral_key);
281 SAFE_FREE(server_identity_key);
282 SAFE_FREE(server_signature);
283 return SET_ERRNO(ERROR_CONFIG,
284 "CRITICAL SECURITY ERROR: Failed to create known_hosts "
285 "file! This is a security vulnerability - the "
286 "program cannot track known hosts. Please check file "
287 "permissions and ensure the program can write to: %s",
289 }
290 log_debug("Server host added to known_hosts successfully");
291 } else if (known_host_result == 1) {
292
293 log_info("Server host key verified from known_hosts - connection secure");
294 } else {
295
296 if (payload) {
298 }
299 SAFE_FREE(server_ephemeral_key);
300 SAFE_FREE(server_identity_key);
301 SAFE_FREE(server_signature);
302 return SET_ERRNO(known_host_result, "SECURITY: known_hosts verification failed with error code %d",
303 known_host_result);
304 }
305 }
306 } else if (payload_len == ctx->crypto_ctx.public_key_size) {
307
308 log_debug("Received simple KEY_EXCHANGE_INIT (%zu bytes) - server has no "
309 "identity key",
310 payload_len);
311 memcpy(server_ephemeral_key, payload, ctx->crypto_ctx.public_key_size);
312
313
314 memset(server_identity_key, 0, ctx->crypto_ctx.auth_public_key_size);
315 memset(server_signature, 0, ctx->crypto_ctx.signature_size);
316
317
318 ctx->server_uses_client_auth = false;
319
320 log_debug("Received ephemeral key (simple format)");
321
322
323
324
325
326
327 if (ctx->server_ip[0] == '\0' || ctx->server_port <= 0) {
328 SAFE_FREE(server_ephemeral_key);
329 SAFE_FREE(server_identity_key);
330 SAFE_FREE(server_signature);
331 return SET_ERRNO(ERROR_CRYPTO, "Server IP or port not set, cannot check known_hosts");
332 }
333
334
335 bool skip_known_hosts = false;
336 asciichat_error_t known_host_result = ASCIICHAT_OK;
337 const char *env_skip_known_hosts_checking =
platform_getenv(
"ASCII_CHAT_INSECURE_NO_HOST_IDENTITY_CHECK");
338 if (env_skip_known_hosts_checking && strcmp(env_skip_known_hosts_checking, STR_ONE) == 0) {
339 log_warn("Skipping known_hosts checking. This is a security vulnerability.");
340 skip_known_hosts = true;
341 }
342#ifndef NDEBUG
343
345 log_warn("Skipping known_hosts checking (CLAUDECODE set in debug build).");
346 skip_known_hosts = true;
347 }
348#endif
349 else {
351 }
352
353 if (skip_known_hosts || known_host_result == 1) {
354
355 log_info("SECURITY: Server IP %s:%u is known (no-identity entry found) - connection verified", ctx->server_ip,
356 ctx->server_port);
357 } else if (known_host_result == ASCIICHAT_OK) {
358
359 log_warn("SECURITY: Unknown server IP %s:%u with no identity key\n"
360 "This connection is vulnerable to man-in-the-middle attacks\n"
361 "Anyone can intercept your connection and read your data",
362 ctx->server_ip, ctx->server_port);
363
365 if (payload) {
367 }
368 SAFE_FREE(server_ephemeral_key);
369 SAFE_FREE(server_identity_key);
370 SAFE_FREE(server_signature);
371 return SET_ERRNO(ERROR_CRYPTO, "User declined to connect to unknown server without identity key");
372 }
373
374
375
376 uint8_t zero_key[ZERO_KEY_SIZE] = {0};
377 if (
add_known_host(ctx->server_ip, ctx->server_port, zero_key) != ASCIICHAT_OK) {
378 if (payload) {
380 }
381 SAFE_FREE(server_ephemeral_key);
382 SAFE_FREE(server_identity_key);
383 SAFE_FREE(server_signature);
384 return SET_ERRNO(ERROR_CONFIG,
385 "CRITICAL SECURITY ERROR: Failed to create known_hosts "
386 "file! This is a security vulnerability - the "
387 "program cannot track known hosts. Please check file "
388 "permissions and ensure the program can write to: %s",
390 }
391 log_debug("Server host added to known_hosts successfully");
392 } else if (known_host_result == ERROR_CRYPTO_VERIFICATION) {
393
394 log_warn("SECURITY: Server previously had identity key but now has none - potential security issue");
395 if (payload) {
397 }
398 SAFE_FREE(server_ephemeral_key);
399 SAFE_FREE(server_identity_key);
400 SAFE_FREE(server_signature);
401 return SET_ERRNO(ERROR_CRYPTO_VERIFICATION, "Server key configuration changed - potential security issue");
402 } else {
403
404 if (payload) {
406 }
407 SAFE_FREE(server_ephemeral_key);
408 SAFE_FREE(server_identity_key);
409 SAFE_FREE(server_signature);
410 return SET_ERRNO(ERROR_CRYPTO, "Failed to verify server IP address");
411 }
412 } else {
413 if (payload) {
415 }
416 SAFE_FREE(server_ephemeral_key);
417 SAFE_FREE(server_identity_key);
418 SAFE_FREE(server_signature);
419 return SET_ERRNO(ERROR_NETWORK_PROTOCOL,
420 "Invalid KEY_EXCHANGE_INIT size: %zu bytes (expected %zu or "
421 "%zu). This indicates: Protocol violation "
422 "or incompatible server version, Potential man-in-the-middle "
423 "attack, Network corruption",
424 payload_len, expected_auth_size, ctx->crypto_ctx.public_key_size);
425
426 }
427
428
430 if (payload) {
432 }
433 if (crypto_result != CRYPTO_OK) {
434 SAFE_FREE(server_ephemeral_key);
435 SAFE_FREE(server_identity_key);
436 SAFE_FREE(server_signature);
437 return SET_ERRNO(ERROR_CRYPTO, "Failed to set peer public key and derive shared secret: %s",
439 }
440
441
442 bool client_has_identity_key = (ctx->client_private_key.type == KEY_TYPE_ED25519);
443
444
445
446
447
448 bool server_has_identity = (ctx->crypto_ctx.auth_public_key_size > 0 && ctx->crypto_ctx.signature_size > 0);
449 bool server_requires_auth = server_has_identity || ctx->require_client_auth;
450
451 if (server_requires_auth) {
452
453
454
455 size_t ed25519_pubkey_size = ED25519_PUBLIC_KEY_SIZE;
456 size_t ed25519_sig_size = ED25519_SIGNATURE_SIZE;
457 size_t response_size = ctx->crypto_ctx.public_key_size + ed25519_pubkey_size + ed25519_sig_size;
458
459
460 uint8_t gpg_key_id_len = 0;
461 if (ctx->client_gpg_key_id[0] != '\0') {
462 gpg_key_id_len = (uint8_t)strlen(ctx->client_gpg_key_id);
463 if (gpg_key_id_len > 40) {
464 gpg_key_id_len = 40;
465 }
466 response_size += 1 + gpg_key_id_len;
467 } else {
468 response_size += 1;
469 }
470
471 uint8_t *key_response = SAFE_MALLOC(response_size, uint8_t *);
472 size_t offset = 0;
473
474
475 memcpy(key_response + offset, ctx->crypto_ctx.public_key,
476 ctx->crypto_ctx.public_key_size);
477 offset += ctx->crypto_ctx.public_key_size;
478
479 if (client_has_identity_key) {
480
481 memcpy(key_response + offset, ctx->client_private_key.public_key, ed25519_pubkey_size);
482 offset += ed25519_pubkey_size;
483
484
485 if (
ed25519_sign_message(&ctx->client_private_key, ctx->crypto_ctx.public_key, ctx->crypto_ctx.public_key_size,
486 key_response + offset) != 0) {
487 SAFE_FREE(key_response);
488 SAFE_FREE(server_ephemeral_key);
489 SAFE_FREE(server_identity_key);
490 SAFE_FREE(server_signature);
491 return SET_ERRNO(ERROR_CRYPTO, "Failed to sign client ephemeral key");
492 }
493 offset += ed25519_sig_size;
494 } else {
495
496 memset(key_response + offset, 0, ed25519_pubkey_size);
497 offset += ed25519_pubkey_size;
498 memset(key_response + offset, 0, ed25519_sig_size);
499 offset += ed25519_sig_size;
500 }
501
502
503 key_response[offset] = gpg_key_id_len;
504 offset += 1;
505
506
507 if (gpg_key_id_len > 0) {
508 memcpy(key_response + offset, ctx->client_gpg_key_id, gpg_key_id_len);
509 offset += gpg_key_id_len;
510 log_debug("Including client GPG key ID in KEY_EXCHANGE_RESPONSE: %.*s", gpg_key_id_len, ctx->client_gpg_key_id);
511 }
512
514 if (result != 0) {
515 SAFE_FREE(key_response);
516 SAFE_FREE(server_ephemeral_key);
517 SAFE_FREE(server_identity_key);
518 SAFE_FREE(server_signature);
519 return SET_ERRNO(ERROR_NETWORK, "Failed to send KEY_EXCHANGE_RESPONSE packet");
520 }
521
522
523 sodium_memzero(key_response, response_size);
524 SAFE_FREE(key_response);
525 } else {
526
527
529 ctx->crypto_ctx.public_key_size, 0);
530 if (result != 0) {
531 SAFE_FREE(server_ephemeral_key);
532 SAFE_FREE(server_identity_key);
533 SAFE_FREE(server_signature);
534 return SET_ERRNO(ERROR_NETWORK, "Failed to send KEY_EXCHANGE_RESPONSE packet");
535 }
536 }
537
538 ctx->state = CRYPTO_HANDSHAKE_KEY_EXCHANGE;
539
540
541 SAFE_FREE(server_ephemeral_key);
542 SAFE_FREE(server_identity_key);
543 SAFE_FREE(server_signature);
544
545 return ASCIICHAT_OK;
546}
asciichat_error_t parse_public_keys(const char *input, public_key_t *keys_out, size_t *num_keys, size_t max_keys)
bool prompt_unknown_host(const char *server_ip, uint16_t port, const uint8_t server_key[32])
const char * get_known_hosts_path(void)
Get the path to the known_hosts file.
bool prompt_unknown_host_no_identity(const char *server_ip, uint16_t port)
asciichat_error_t check_known_host_no_identity(const char *server_ip, uint16_t port)
asciichat_error_t add_known_host(const char *server_ip, uint16_t port, const uint8_t server_key[32])
bool display_mitm_warning(const char *server_ip, uint16_t port, const uint8_t expected_key[32], const uint8_t received_key[32])
asciichat_error_t check_known_host(const char *server_ip, uint16_t port, const uint8_t server_key[32])
crypto_result_t crypto_set_peer_public_key(crypto_context_t *ctx, const uint8_t *peer_public_key)
asciichat_error_t packet_send_via_transport(acip_transport_t *transport, packet_type_t type, const void *payload, size_t payload_len, uint32_t client_id)
Send packet via transport with proper header (exported for generic wrappers)
asciichat_error_t ed25519_verify_signature(const uint8_t public_key[32], const uint8_t *message, size_t message_len, const uint8_t signature[64], const char *gpg_key_id)
asciichat_error_t ed25519_sign_message(const private_key_t *key, const uint8_t *message, size_t message_len, uint8_t signature[64])
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
const char * platform_getenv(const char *name)