ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
lib/crypto/handshake/client.c
Go to the documentation of this file.
1
7#include <ascii-chat/crypto/handshake/client.h>
8#include <ascii-chat/asciichat_errno.h>
9#include <ascii-chat/buffer_pool.h>
10#include <ascii-chat/common.h>
11#include <ascii-chat/util/endian.h>
12#include <ascii-chat/util/ip.h>
13#include <ascii-chat/crypto/crypto.h>
14#include <ascii-chat/crypto/known_hosts.h>
15#include <ascii-chat/crypto/gpg/gpg.h>
16#include <ascii-chat/network/packet.h>
17#include <ascii-chat/network/acip/transport.h>
18#include <ascii-chat/network/acip/send.h>
19#include <ascii-chat/util/password.h>
20#include <stdio.h>
21#include <string.h>
22#ifndef _WIN32
23#include <sys/socket.h>
24#include <netinet/in.h>
25#include <arpa/inet.h>
26#else
27#include <ws2tcpip.h>
28#endif
29
30// Client: Process server's public key and send our public key
31asciichat_error_t crypto_handshake_client_key_exchange(crypto_handshake_context_t *ctx, acip_transport_t *transport,
32 packet_type_t packet_type, const uint8_t *payload,
33 size_t payload_len) {
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 // Note: Packet already received by ACIP handler
42 int result;
43
44 // Verify packet type
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 // Check payload size - only authenticated format supported
54 // Authenticated: public_key_size + auth_public_key_size + signature_size bytes
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 // Use the crypto context's public key size to ensure compatibility
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 // Validate packet size using session parameters
80 asciichat_error_t validation_result =
81 crypto_handshake_validate_packet_size(ctx, PACKET_TYPE_CRYPTO_KEY_EXCHANGE_INIT, payload_len);
82 if (validation_result != ASCIICHAT_OK) {
83 if (payload) {
84 buffer_pool_free(NULL, payload, payload_len);
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 // Check if server is using authentication (auth_public_key_size > 0 means
93 // authenticated format)
94 if (ctx->crypto_ctx.auth_public_key_size > 0 && payload_len == expected_auth_size) {
95 // Authenticated format:
96 // [ephemeral:public_key_size][identity:auth_public_key_size][signature:signature_size]
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 // Server is using client authentication
104 ctx->server_uses_client_auth = true;
105
106 // DEBUG: Print identity key received
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 // DEBUG: Print ephemeral key and signature
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 // Verify signature: server identity signed the ephemeral key
130 log_debug("Verifying server's signature over ephemeral key (already logged above)");
131
132 // If client didn't specify --server-key, skip signature verification
133 // (client doesn't care about server identity verification)
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 // Extract GPG key ID from expected_server_key if it's a GPG key (gpg:KEYID format)
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 // Accept 8, 16, or 40 character GPG key IDs (short, long, or full fingerprint)
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
150 if (ed25519_verify_signature(server_identity_key, server_ephemeral_key, ctx->crypto_ctx.public_key_size,
151 server_signature, gpg_key_id) != 0) {
152 if (payload) {
153 buffer_pool_free(NULL, payload, payload_len);
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 // Verify server identity against expected key if --server-key is specified
167 if (ctx->verify_server_key && strlen(ctx->expected_server_key) > 0) {
168 // Parse ALL expected server keys (github:/gitlab: may have multiple keys)
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) {
174 buffer_pool_free(NULL, payload, payload_len);
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 // Compare server's IDENTITY key against ALL expected keys (match any one)
187 // This supports users with multiple SSH keys (e.g., different machines)
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) {
199 buffer_pool_free(NULL, payload, payload_len);
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 // Note: Server IP resolution now handled by caller (TCP transport layer)
215 // For WebSocket/WebRTC transports, server_ip should be set by the transport
216 // before handshake begins. TCP clients will use the legacy wrapper which
217 // handles this.
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 // Check known_hosts for this server (if we have server IP and port)
225 // Check if known_hosts verification should be skipped
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 // In debug builds, also skip for Claude Code (LLM automation can't do interactive prompts)
235 else if (platform_getenv("CLAUDECODE")) {
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 // Key mismatch - MITM attack detected! Prompt user for confirmation
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}; // We don't have the stored key easily
248 // accessible, use zeros for now
249 if (!display_mitm_warning(ctx->server_ip, ctx->server_port, stored_key, server_identity_key)) {
250 // User declined to continue - ABORT connection for security
251 if (payload) {
252 buffer_pool_free(NULL, payload, payload_len);
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 // User accepted the risk - continue with connection
261 log_warn("SECURITY WARNING: User accepted MITM risk - continuing with connection");
262 } else if (known_host_result == ASCIICHAT_OK) {
263 // Unknown host (first connection) - prompt user to verify fingerprint
264 if (!prompt_unknown_host(ctx->server_ip, ctx->server_port, server_identity_key)) {
265 // User declined to add host - ABORT connection
266 if (payload) {
267 buffer_pool_free(NULL, payload, payload_len);
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 // User accepted - add to known_hosts
276 if (add_known_host(ctx->server_ip, ctx->server_port, server_identity_key) != ASCIICHAT_OK) {
277 if (payload) {
278 buffer_pool_free(NULL, payload, payload_len);
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 // Key matches - connection is secure!
293 log_info("Server host key verified from known_hosts - connection secure");
294 } else {
295 // Unexpected error code from check_known_host
296 if (payload) {
297 buffer_pool_free(NULL, payload, payload_len);
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 // Simple format: just ephemeral key (no identity key)
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 // Clear identity key and signature for simple format
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 // Server is not using client authentication in simple mode
318 ctx->server_uses_client_auth = false;
319
320 log_debug("Received ephemeral key (simple format)");
321
322 // SECURITY: For servers without identity keys, we implement a different security model:
323 // 1. Verify IP address matches known_hosts entry
324 // 2. Always require user confirmation (no silent connections)
325 // 3. Store server fingerprint for future verification
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 // Check if this server was previously connected to (IP verification)
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 // In debug builds, also skip for Claude Code (LLM automation can't do interactive prompts)
344 else if (platform_getenv("CLAUDECODE")) {
345 log_warn("Skipping known_hosts checking (CLAUDECODE set in debug build).");
346 skip_known_hosts = true;
347 }
348#endif
349 else {
350 known_host_result = check_known_host_no_identity(ctx->server_ip, ctx->server_port);
351 }
352
353 if (skip_known_hosts || known_host_result == 1) {
354 // Server IP is known and verified - allow connection without warnings
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 // Server IP is unknown - require user confirmation
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
364 if (!prompt_unknown_host_no_identity(ctx->server_ip, ctx->server_port)) {
365 if (payload) {
366 buffer_pool_free(NULL, payload, payload_len);
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 // User accepted - add to known_hosts as no-identity entry
375 // For servers without identity keys, pass zero key to indicate no-identity
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) {
379 buffer_pool_free(NULL, payload, payload_len);
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 // Server previously had identity key but now has none - potential security issue
394 log_warn("SECURITY: Server previously had identity key but now has none - potential security issue");
395 if (payload) {
396 buffer_pool_free(NULL, payload, payload_len);
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 // Other error checking known_hosts (e.g., ERROR_INVALID_PARAM)
404 if (payload) {
405 buffer_pool_free(NULL, payload, payload_len);
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) {
414 buffer_pool_free(NULL, payload, payload_len);
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 // retry
426 }
427
428 // Set peer's public key (EPHEMERAL X25519) - this also derives the shared secret
429 crypto_result_t crypto_result = crypto_set_peer_public_key(&ctx->crypto_ctx, server_ephemeral_key);
430 if (payload) {
431 buffer_pool_free(NULL, payload, payload_len);
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",
438 crypto_result_to_string(crypto_result));
439 }
440
441 // Determine if client has an identity key
442 bool client_has_identity_key = (ctx->client_private_key.type == KEY_TYPE_ED25519);
443
444 // Send authenticated response if server has identity key (auth_public_key_size > 0)
445 // OR if server requires client authentication (require_client_auth)
446 // Note: server_uses_client_auth is set when server has identity key, but we should
447 // send authenticated response when server has identity key regardless of client auth requirement
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 // Send authenticated packet:
453 // [ephemeral:kex_size][identity:auth_size][signature:sig_size][gpg_key_id_len:1][gpg_key_id:0-16]
454 // Use Ed25519 sizes since client has Ed25519 key
455 size_t ed25519_pubkey_size = ED25519_PUBLIC_KEY_SIZE; // Ed25519 public key is always 32 bytes
456 size_t ed25519_sig_size = ED25519_SIGNATURE_SIZE; // Ed25519 signature is always 64 bytes
457 size_t response_size = ctx->crypto_ctx.public_key_size + ed25519_pubkey_size + ed25519_sig_size;
458
459 // Check if client has a GPG key ID to send
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; // Truncate to max length (full fingerprint)
465 }
466 response_size += 1 + gpg_key_id_len; // 1 byte for length + key ID
467 } else {
468 response_size += 1; // Just the length byte (0)
469 }
470
471 uint8_t *key_response = SAFE_MALLOC(response_size, uint8_t *);
472 size_t offset = 0;
473
474 // Copy ephemeral key
475 memcpy(key_response + offset, ctx->crypto_ctx.public_key,
476 ctx->crypto_ctx.public_key_size); // X25519 ephemeral for encryption
477 offset += ctx->crypto_ctx.public_key_size;
478
479 if (client_has_identity_key) {
480 // Client has identity key - send it with signature
481 memcpy(key_response + offset, ctx->client_private_key.public_key, ed25519_pubkey_size); // Ed25519 identity
482 offset += ed25519_pubkey_size;
483
484 // Sign ephemeral key with client identity key
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 // Client has no identity key - send null identity and null signature
496 memset(key_response + offset, 0, ed25519_pubkey_size); // Null identity
497 offset += ed25519_pubkey_size;
498 memset(key_response + offset, 0, ed25519_sig_size); // Null signature
499 offset += ed25519_sig_size;
500 }
501
502 // Append GPG key ID length
503 key_response[offset] = gpg_key_id_len;
504 offset += 1;
505
506 // Append GPG key ID if present
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
513 result = packet_send_via_transport(transport, PACKET_TYPE_CRYPTO_KEY_EXCHANGE_RESP, key_response, response_size, 0);
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 // Zero out the buffer before freeing
523 sodium_memzero(key_response, response_size);
524 SAFE_FREE(key_response);
525 } else {
526 // Send X25519 encryption key only to server (no identity key)
527 // Format: [X25519 pubkey (kex_size)] = kex_size bytes total
528 result = packet_send_via_transport(transport, PACKET_TYPE_CRYPTO_KEY_EXCHANGE_RESP, ctx->crypto_ctx.public_key,
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 // Free temporary buffers before successful return
541 SAFE_FREE(server_ephemeral_key);
542 SAFE_FREE(server_identity_key);
543 SAFE_FREE(server_signature);
544
545 return ASCIICHAT_OK;
546}
547// Helper: Send password-based authentication response with mutual auth
548static asciichat_error_t send_password_auth_response(crypto_handshake_context_t *ctx, acip_transport_t *transport,
549 const uint8_t *nonce, const char *auth_context) {
550 // Ensure shared secret is derived before computing password HMAC
551 // This is critical for password HMAC computation which binds to the shared secret
552 if (!ctx->crypto_ctx.key_exchange_complete) {
553 return SET_ERRNO(ERROR_CRYPTO, "Failed to compute password HMAC - key exchange not complete");
554 }
555
556 // Compute HMAC bound to shared_secret (MITM protection)
557 uint8_t hmac_response[HMAC_SHA256_SIZE]; // Maximum size buffer
558 crypto_result_t crypto_result = crypto_compute_auth_response(&ctx->crypto_ctx, nonce, hmac_response);
559 if (crypto_result != CRYPTO_OK) {
560 return SET_ERRNO(ERROR_CRYPTO, "Failed to compute HMAC response: %s", crypto_result_to_string(crypto_result));
561 }
562
563 // Generate client challenge nonce for mutual authentication
564 crypto_result = crypto_generate_nonce(ctx->client_challenge_nonce);
565 if (crypto_result != CRYPTO_OK) {
566 return SET_ERRNO(ERROR_CRYPTO, "Failed to generate client challenge nonce: %s",
567 crypto_result_to_string(crypto_result));
568 }
569
570 // Combine HMAC + client nonce (hmac_size + auth_challenge_size bytes)
571 // Use ctx->crypto_ctx.hmac_size and ctx->crypto_ctx.auth_challenge_size (negotiated during handshake)
572 size_t auth_packet_size = ctx->crypto_ctx.hmac_size + ctx->crypto_ctx.auth_challenge_size;
573 uint8_t auth_packet[HMAC_SHA256_SIZE + HMAC_SHA256_SIZE]; // Maximum size buffer (hmac + challenge)
574 memcpy(auth_packet, hmac_response, ctx->crypto_ctx.hmac_size);
575 memcpy(auth_packet + ctx->crypto_ctx.hmac_size, ctx->client_challenge_nonce, ctx->crypto_ctx.auth_challenge_size);
576
577 log_debug("Sending AUTH_RESPONSE packet with HMAC + client nonce (%zu bytes) - %s", auth_packet_size, auth_context);
578 int result = packet_send_via_transport(transport, PACKET_TYPE_CRYPTO_AUTH_RESPONSE, auth_packet, auth_packet_size, 0);
579 if (result != 0) {
580 return SET_ERRNO(ERROR_NETWORK, "Failed to send AUTH_RESPONSE packet");
581 }
582
583 return ASCIICHAT_OK;
584}
585
586// Helper: Send Ed25519 signature-based authentication response with mutual auth
587static asciichat_error_t send_key_auth_response(crypto_handshake_context_t *ctx, acip_transport_t *transport,
588 const uint8_t *nonce, const char *auth_context) {
589 // Sign the challenge with our Ed25519 private key
590 uint8_t signature[ED25519_SIGNATURE_SIZE]; // Maximum size buffer
591 asciichat_error_t sign_result =
592 ed25519_sign_message(&ctx->client_private_key, nonce, ctx->crypto_ctx.auth_challenge_size, signature);
593 if (sign_result != ASCIICHAT_OK) {
594 return SET_ERRNO(ERROR_CRYPTO, "Failed to sign challenge with Ed25519 key");
595 }
596
597 // Generate client challenge nonce for mutual authentication
598 crypto_result_t crypto_result = crypto_generate_nonce(ctx->client_challenge_nonce);
599 if (crypto_result != CRYPTO_OK) {
600 sodium_memzero(signature, sizeof(signature));
601 return SET_ERRNO(ERROR_CRYPTO, "Failed to generate client challenge nonce: %s",
602 crypto_result_to_string(crypto_result));
603 }
604
605 // Combine signature + client nonce + optional GPG key ID
606 // Packet format: [signature:64][nonce:32][gpg_key_id_len:1][gpg_key_id:0-40]
607 size_t auth_packet_size = ctx->crypto_ctx.signature_size + ctx->crypto_ctx.auth_challenge_size;
608
609 // Check if client has a GPG key ID to send
610 uint8_t gpg_key_id_len = 0;
611 if (ctx->client_gpg_key_id[0] != '\0') {
612 gpg_key_id_len = (uint8_t)strlen(ctx->client_gpg_key_id);
613 if (gpg_key_id_len > 40) {
614 gpg_key_id_len = 40; // Truncate to max length (full fingerprint)
615 }
616 auth_packet_size += 1 + gpg_key_id_len; // 1 byte for length + key ID
617 } else {
618 auth_packet_size += 1; // Just the length byte (0)
619 }
620
621 uint8_t auth_packet[ED25519_SIGNATURE_SIZE + HMAC_SHA256_SIZE +
622 41]; // Maximum size buffer (signature + challenge + len + key_id[40])
623 size_t offset = 0;
624
625 // Copy signature
626 memcpy(auth_packet + offset, signature, ctx->crypto_ctx.signature_size);
627 offset += ctx->crypto_ctx.signature_size;
628
629 // Copy client nonce
630 memcpy(auth_packet + offset, ctx->client_challenge_nonce, ctx->crypto_ctx.auth_challenge_size);
631 offset += ctx->crypto_ctx.auth_challenge_size;
632
633 // Copy GPG key ID length
634 auth_packet[offset] = gpg_key_id_len;
635 offset += 1;
636
637 // Copy GPG key ID if present
638 if (gpg_key_id_len > 0) {
639 memcpy(auth_packet + offset, ctx->client_gpg_key_id, gpg_key_id_len);
640 offset += gpg_key_id_len;
641 log_debug("Including client GPG key ID in AUTH_RESPONSE: %.*s", gpg_key_id_len, ctx->client_gpg_key_id);
642 }
643
644 sodium_memzero(signature, sizeof(signature));
645
646 log_debug("Sending AUTH_RESPONSE packet with Ed25519 signature + client "
647 "nonce + GPG key ID (%zu bytes) - %s",
648 auth_packet_size, auth_context);
649 int result = packet_send_via_transport(transport, PACKET_TYPE_CRYPTO_AUTH_RESPONSE, auth_packet, auth_packet_size, 0);
650 if (result != 0) {
651 return SET_ERRNO(ERROR_NETWORK, "Failed to send AUTH_RESPONSE packet");
652 }
653
654 return ASCIICHAT_OK;
655}
656// Client: Process auth challenge and send response
657asciichat_error_t crypto_handshake_client_auth_response(crypto_handshake_context_t *ctx, acip_transport_t *transport,
658 packet_type_t packet_type, const uint8_t *payload,
659 size_t payload_len) {
660 if (!ctx || ctx->state != CRYPTO_HANDSHAKE_KEY_EXCHANGE) {
661 return SET_ERRNO(ERROR_INVALID_STATE, "Invalid state: ctx=%p, state=%d", ctx, ctx ? ctx->state : -1);
662 }
663 if (!transport) {
664 return SET_ERRNO(ERROR_INVALID_PARAM, "transport is NULL");
665 }
666
667 // Note: Packet already received by ACIP handler
668 int result;
669
670 // If server sent HANDSHAKE_COMPLETE, authentication was skipped (client has
671 // no key)
672 if (packet_type == PACKET_TYPE_CRYPTO_HANDSHAKE_COMPLETE) {
673 if (payload) {
674 buffer_pool_free(NULL, payload, payload_len);
675 }
676 ctx->state = CRYPTO_HANDSHAKE_READY;
677 ctx->crypto_ctx.handshake_complete = true; // Mark crypto context as ready for rekeying
678 log_debug("Crypto handshake completed successfully (no authentication required)");
679 return ASCIICHAT_OK;
680 }
681
682 // If server sent AUTH_FAILED, client is not authorized
683 if (packet_type == PACKET_TYPE_CRYPTO_AUTH_FAILED) {
684 if (payload) {
685 buffer_pool_free(NULL, payload, payload_len);
686 }
687 return SET_ERRNO(ERROR_CRYPTO, "Server rejected authentication - client key not authorized");
688 }
689
690 // Otherwise, verify packet type is AUTH_CHALLENGE
691 if (packet_type != PACKET_TYPE_CRYPTO_AUTH_CHALLENGE) {
692 if (payload) {
693 buffer_pool_free(NULL, payload, payload_len);
694 }
695 return SET_ERRNO(ERROR_NETWORK_PROTOCOL,
696 "Expected AUTH_CHALLENGE, HANDSHAKE_COMPLETE, or AUTH_FAILED, "
697 "got packet type %d",
698 packet_type);
699 }
700
701 // Validate packet size using session parameters
702 asciichat_error_t validation_result =
703 crypto_handshake_validate_packet_size(ctx, PACKET_TYPE_CRYPTO_AUTH_CHALLENGE, payload_len);
704 if (validation_result != ASCIICHAT_OK) {
705 if (payload) {
706 buffer_pool_free(NULL, payload, payload_len);
707 }
708 return validation_result;
709 }
710
711 // Parse auth requirement flags
712 uint8_t auth_flags = payload[0];
713
714 // Copy nonce to local buffer before freeing payload
715 // Use auth_challenge_size since that's what the server sent
716 // Note: auth_challenge_size is uint8_t (max 255), buffer is 256 bytes, so always sufficient
717 uint8_t nonce_buffer[256];
718 memcpy(nonce_buffer, payload + 1, ctx->crypto_ctx.auth_challenge_size);
719 const uint8_t *nonce = nonce_buffer;
720
721 log_debug("Server auth requirements: password=%s, client_key=%s",
722 (auth_flags & AUTH_REQUIRE_PASSWORD) ? "required" : "no",
723 (auth_flags & AUTH_REQUIRE_CLIENT_KEY) ? "required" : "no");
724
725 // Check if we can satisfy the server's authentication requirements
726 bool has_password = ctx->crypto_ctx.has_password;
727 bool has_client_key = (ctx->client_private_key.type == KEY_TYPE_ED25519);
728 bool password_required = (auth_flags & AUTH_REQUIRE_PASSWORD);
729 bool client_key_required = (auth_flags & AUTH_REQUIRE_CLIENT_KEY);
730
731 // Provide specific error messages based on what's missing
732 if (password_required && !has_password) {
733 if (client_key_required && !has_client_key) {
734 if (payload) {
735 buffer_pool_free(NULL, payload, payload_len);
736 }
737 return SET_ERRNO(ERROR_CRYPTO, "Server requires both password and client key authentication. Please "
738 "provide --password and --key to authenticate");
739 }
740 // Prompt for password interactively
741 char prompted_password[PASSWORD_BUFFER_SIZE];
742 if (prompt_password("Server password required - please enter password:", prompted_password,
743 sizeof(prompted_password)) != 0) {
744 if (payload) {
745 buffer_pool_free(NULL, payload, payload_len);
746 }
747 return SET_ERRNO(ERROR_CRYPTO, "Failed to read password");
748 }
749
750 // Derive password key from prompted password
751 log_debug("Deriving key from prompted password");
752 crypto_result_t crypto_result = crypto_derive_password_key(&ctx->crypto_ctx, prompted_password);
753 sodium_memzero(prompted_password, sizeof(prompted_password));
754
755 if (crypto_result != CRYPTO_OK) {
756 if (payload) {
757 buffer_pool_free(NULL, payload, payload_len);
758 }
759 return SET_ERRNO(ERROR_CRYPTO, "Failed to derive password key: %s", crypto_result_to_string(crypto_result));
760 }
761
762 // Mark that password auth is now available
763 ctx->crypto_ctx.has_password = true;
764 has_password = true; // Update flag for logic below
765 }
766
767 // Authentication response priority:
768 // NOTE: Identity verification happens during KEY_EXCHANGE phase, not
769 // AUTH_RESPONSE!
770 // 1. If server requires password → MUST send HMAC (hmac_size bytes), error if no password
771 // 2. Else if server requires identity (whitelist) → MUST send Ed25519 signature (signature_size bytes), error if no
772 // key
773 // 3. Else if client has password → send HMAC (optional password auth)
774 // 4. Else if client has SSH key → send Ed25519 signature (optional identity
775 // proof)
776 // 5. Else → no authentication available
777
778 // Clean up payload before any early returns
779 if (payload) {
780 buffer_pool_free(NULL, payload, payload_len);
781 }
782
783 if (password_required) {
784 // Server requires password - HIGHEST PRIORITY
785 // (Identity was already verified in KEY_EXCHANGE phase if whitelist is
786 // enabled)
787 if (!has_password) {
788 return SET_ERRNO(ERROR_CRYPTO, "Server requires password authentication\n"
789 "Please provide --password for this server");
790 }
791
792 result = send_password_auth_response(ctx, transport, nonce, "required password");
793 if (result != ASCIICHAT_OK) {
794 SET_ERRNO(ERROR_NETWORK, "Failed to send password auth response");
795 return result;
796 }
797 } else if (client_key_required) {
798 // Server requires client key (whitelist) - SECOND PRIORITY
799 if (!has_client_key) {
800 return SET_ERRNO(ERROR_CRYPTO, "Server requires client key authentication (whitelist)\n"
801 "Please provide --key with your authorized Ed25519 key");
802 }
803
804 result = send_key_auth_response(ctx, transport, nonce, "required client key");
805 if (result != ASCIICHAT_OK) {
806 SET_ERRNO(ERROR_NETWORK, "Failed to send key auth response");
807 return result;
808 }
809 } else if (has_password) {
810 // No server requirements, but client has password → send HMAC + client
811 // nonce (optional)
812 result = send_password_auth_response(ctx, transport, nonce, "optional password");
813 if (result != ASCIICHAT_OK) {
814 SET_ERRNO(ERROR_NETWORK, "Failed to send password auth response");
815 return result;
816 }
817 } else if (has_client_key) {
818 // No server requirements, but client has SSH key → send Ed25519 signature +
819 // client nonce (optional)
820 result = send_key_auth_response(ctx, transport, nonce, "optional identity");
821 if (result != ASCIICHAT_OK) {
822 SET_ERRNO(ERROR_NETWORK, "Failed to send key auth response");
823 return result;
824 }
825 } else {
826 // No authentication method available
827 // Continue without authentication (server will decide if this is
828 // acceptable)
829 log_debug("No authentication credentials provided - continuing without "
830 "authentication");
831 }
832
833 ctx->state = CRYPTO_HANDSHAKE_AUTHENTICATING;
834
835 return ASCIICHAT_OK;
836}
837// Client: Wait for handshake complete confirmation
838asciichat_error_t crypto_handshake_client_complete(crypto_handshake_context_t *ctx, acip_transport_t *transport,
839 packet_type_t packet_type, const uint8_t *payload,
840 size_t payload_len) {
841 // Accept both KEY_EXCHANGE and AUTHENTICATING states for simple mode compatibility
842 // In simple mode, server skips AUTH_CHALLENGE and sends HANDSHAKE_COMPLETE directly
843 if (!ctx || (ctx->state != CRYPTO_HANDSHAKE_KEY_EXCHANGE && ctx->state != CRYPTO_HANDSHAKE_AUTHENTICATING)) {
844 SET_ERRNO(ERROR_INVALID_STATE, "Invalid state: ctx=%p, state=%d", ctx, ctx ? ctx->state : -1);
845 return ERROR_INVALID_STATE;
846 }
847 if (!transport) {
848 return SET_ERRNO(ERROR_INVALID_PARAM, "transport is NULL");
849 }
850
851 // Note: Packet already received by ACIP handler
852 (void)transport; // Unused parameter (this function only receives, doesn't send)
853
854 // Check packet type
855 if (packet_type == PACKET_TYPE_CRYPTO_AUTH_FAILED) {
856 // Parse the auth failure packet to get specific reasons
857 if (payload_len >= sizeof(auth_failure_packet_t)) {
858 auth_failure_packet_t *failure = (auth_failure_packet_t *)payload;
859 SET_ERRNO(ERROR_CRYPTO_AUTH, "Server rejected authentication:");
860
861 if (failure->reason_flags & AUTH_FAIL_PASSWORD_INCORRECT) {
862 SET_ERRNO(ERROR_CRYPTO_AUTH, " - Incorrect password");
863 }
864 if (failure->reason_flags & AUTH_FAIL_PASSWORD_REQUIRED) {
865 SET_ERRNO(ERROR_CRYPTO_AUTH, " - Server requires a password (use --password)");
866 }
867 if (failure->reason_flags & AUTH_FAIL_CLIENT_KEY_REQUIRED) {
868 SET_ERRNO(ERROR_CRYPTO_AUTH, " - Server requires a whitelisted client key (use --key "
869 "with your SSH key)");
870 }
871 if (failure->reason_flags & AUTH_FAIL_CLIENT_KEY_REJECTED) {
872 SET_ERRNO(ERROR_CRYPTO_AUTH, " - Your client key is not in the server's whitelist");
873 }
874 if (failure->reason_flags & AUTH_FAIL_SIGNATURE_INVALID) {
875 SET_ERRNO(ERROR_CRYPTO_AUTH, " - Client signature verification failed");
876 }
877
878 // Provide helpful guidance
879 if (failure->reason_flags & (AUTH_FAIL_PASSWORD_INCORRECT | AUTH_FAIL_CLIENT_KEY_REQUIRED)) {
880 if ((failure->reason_flags & AUTH_FAIL_PASSWORD_INCORRECT) &&
881 (failure->reason_flags & AUTH_FAIL_CLIENT_KEY_REQUIRED)) {
882 SET_ERRNO(ERROR_CRYPTO_AUTH, "Hint: Server requires BOTH correct password AND "
883 "whitelisted key");
884 } else if (failure->reason_flags & AUTH_FAIL_PASSWORD_INCORRECT) {
885 SET_ERRNO(ERROR_CRYPTO_AUTH, "Hint: Check your password and try again");
886 } else if (failure->reason_flags & AUTH_FAIL_CLIENT_KEY_REQUIRED) {
887 SET_ERRNO(ERROR_CRYPTO_AUTH, "Hint: Provide your SSH key with --key ~/.ssh/id_ed25519");
888 } else if (failure->reason_flags & AUTH_FAIL_CLIENT_KEY_REJECTED) {
889 SET_ERRNO(ERROR_CRYPTO_AUTH, "Hint: Your key needs to be added to the server's whitelist");
890 }
891 }
892 } else {
893 SET_ERRNO(ERROR_CRYPTO_AUTH, "Server rejected authentication (no details provided)");
894 }
895 if (payload) {
896 buffer_pool_free(NULL, payload, payload_len);
897 }
898 return SET_ERRNO(ERROR_CRYPTO_AUTH,
899 "Server authentication failed - incorrect HMAC"); // Special code for
900 // auth failure - do
901 // not retry
902 }
903
904 // Handle no-auth flow: server sends HANDSHAKE_COMPLETE directly
905 if (packet_type == PACKET_TYPE_CRYPTO_HANDSHAKE_COMPLETE) {
906 if (payload) {
907 buffer_pool_free(NULL, payload, payload_len);
908 }
909 ctx->state = CRYPTO_HANDSHAKE_READY;
910 log_info("Handshake complete (no authentication required)");
911 return ASCIICHAT_OK;
912 }
913
914 // Handle with-auth flow: server sends SERVER_AUTH_RESP after authentication
915 if (packet_type != PACKET_TYPE_CRYPTO_SERVER_AUTH_RESP) {
916 if (payload) {
917 buffer_pool_free(NULL, payload, payload_len);
918 }
919 return SET_ERRNO(ERROR_NETWORK_PROTOCOL,
920 "Expected HANDSHAKE_COMPLETE, SERVER_AUTH_RESPONSE, or AUTH_FAILED, got packet type %d",
921 packet_type);
922 }
923
924 // Verify server's HMAC for mutual authentication
925 // Use ctx->crypto_ctx.hmac_size (negotiated during handshake) rather than SERVER_AUTH_RESPONSE_SIZE constant
926 if (payload_len != ctx->crypto_ctx.hmac_size) {
927 if (payload) {
928 buffer_pool_free(NULL, payload, payload_len);
929 }
930 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Invalid SERVER_AUTH_RESPONSE size: %zu bytes (expected %u)", payload_len,
931 ctx->crypto_ctx.hmac_size);
932 }
933
934 // Verify server's HMAC (binds to DH shared_secret to prevent MITM)
935 if (!crypto_verify_auth_response(&ctx->crypto_ctx, ctx->client_challenge_nonce, payload)) {
936 SET_ERRNO(ERROR_CRYPTO_AUTH, "SECURITY: Server authentication failed - incorrect HMAC");
937 SET_ERRNO(ERROR_CRYPTO_AUTH, "This may indicate a man-in-the-middle attack!");
938 if (payload) {
939 buffer_pool_free(NULL, payload, payload_len);
940 }
941 return SET_ERRNO(ERROR_CRYPTO_AUTH,
942 "Server authentication failed - incorrect HMAC"); // Authentication
943 // failure - do not
944 // retry
945 }
946
947 if (payload) {
948 buffer_pool_free(NULL, payload, payload_len);
949 }
950
951 ctx->state = CRYPTO_HANDSHAKE_READY;
952 log_info("Server authentication successful - mutual authentication complete");
953
954 return ASCIICHAT_OK;
955}
956
957// =============================================================================
958// Legacy TCP Socket Wrappers (backward compatibility)
959// =============================================================================
960// These wrappers maintain the old socket-based interface for TCP clients
961// that do handshake BEFORE creating ACIP transport. Will be removed in Phase 5.
962
966asciichat_error_t crypto_handshake_client_key_exchange_socket(crypto_handshake_context_t *ctx, socket_t client_socket) {
967 // Receive packet using old method
968 packet_type_t packet_type;
969 uint8_t *payload = NULL;
970 size_t payload_len = 0;
971 int result = receive_packet(client_socket, &packet_type, (void **)&payload, &payload_len);
972 if (result != ASCIICHAT_OK) {
973 return SET_ERRNO(ERROR_NETWORK, "Failed to receive KEY_EXCHANGE_INIT packet");
974 }
975
976 // Create temporary TCP transport
977 acip_transport_t *temp_transport = acip_tcp_transport_create(client_socket, NULL);
978 if (!temp_transport) {
979 if (payload) {
980 buffer_pool_free(NULL, payload, payload_len);
981 }
982 return SET_ERRNO(ERROR_NETWORK, "Failed to create temporary transport");
983 }
984
985 // Call new function (takes ownership of payload and will free it)
986 asciichat_error_t handshake_result =
987 crypto_handshake_client_key_exchange(ctx, temp_transport, packet_type, payload, payload_len);
988
989 // Destroy temporary transport
990 acip_transport_destroy(temp_transport);
991
992 return handshake_result;
993}
994
998asciichat_error_t crypto_handshake_client_auth_response_socket(crypto_handshake_context_t *ctx,
999 socket_t client_socket) {
1000 // Receive packet using old method
1001 packet_type_t packet_type;
1002 uint8_t *payload = NULL;
1003 size_t payload_len = 0;
1004 int result = receive_packet(client_socket, &packet_type, (void **)&payload, &payload_len);
1005 if (result != ASCIICHAT_OK) {
1006 return SET_ERRNO(ERROR_NETWORK, "Failed to receive packet from server");
1007 }
1008
1009 // Create temporary TCP transport
1010 acip_transport_t *temp_transport = acip_tcp_transport_create(client_socket, NULL);
1011 if (!temp_transport) {
1012 if (payload) {
1013 buffer_pool_free(NULL, payload, payload_len);
1014 }
1015 return SET_ERRNO(ERROR_NETWORK, "Failed to create temporary transport");
1016 }
1017
1018 // Call new function (takes ownership of payload and will free it)
1019 asciichat_error_t handshake_result =
1020 crypto_handshake_client_auth_response(ctx, temp_transport, packet_type, payload, payload_len);
1021
1022 // Destroy temporary transport
1023 acip_transport_destroy(temp_transport);
1024
1025 return handshake_result;
1026}
1027
1031asciichat_error_t crypto_handshake_client_complete_socket(crypto_handshake_context_t *ctx, socket_t client_socket) {
1032 // Receive packet using old method
1033 packet_type_t packet_type;
1034 uint8_t *payload = NULL;
1035 size_t payload_len = 0;
1036 int result = receive_packet(client_socket, &packet_type, (void **)&payload, &payload_len);
1037 if (result != ASCIICHAT_OK) {
1038 return SET_ERRNO(ERROR_NETWORK, "Failed to receive handshake completion packet");
1039 }
1040
1041 // Create temporary TCP transport
1042 acip_transport_t *temp_transport = acip_tcp_transport_create(client_socket, NULL);
1043 if (!temp_transport) {
1044 if (payload) {
1045 buffer_pool_free(NULL, payload, payload_len);
1046 }
1047 return SET_ERRNO(ERROR_NETWORK, "Failed to create temporary transport");
1048 }
1049
1050 // Call new function (takes ownership of payload and will free it)
1051 asciichat_error_t handshake_result =
1052 crypto_handshake_client_complete(ctx, temp_transport, packet_type, payload, payload_len);
1053
1054 // Destroy temporary transport
1055 acip_transport_destroy(temp_transport);
1056
1057 return handshake_result;
1058}
void buffer_pool_free(buffer_pool_t *pool, void *data, size_t size)
asciichat_error_t crypto_handshake_validate_packet_size(const crypto_handshake_context_t *ctx, uint16_t packet_type, size_t packet_size)
int socket_t
asciichat_error_t parse_public_keys(const char *input, public_key_t *keys_out, size_t *num_keys, size_t max_keys)
Definition keys.c:345
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.
Definition known_hosts.c:47
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])
Definition known_hosts.c:78
crypto_result_t crypto_set_peer_public_key(crypto_context_t *ctx, const uint8_t *peer_public_key)
crypto_result_t crypto_compute_auth_response(const crypto_context_t *ctx, const uint8_t nonce[32], uint8_t hmac_out[32])
crypto_result_t crypto_generate_nonce(uint8_t nonce[32])
const char * crypto_result_to_string(crypto_result_t result)
crypto_result_t crypto_derive_password_key(crypto_context_t *ctx, const char *password)
bool crypto_verify_auth_response(const crypto_context_t *ctx, const uint8_t nonce[32], const uint8_t expected_hmac[32])
asciichat_error_t crypto_handshake_client_complete(crypto_handshake_context_t *ctx, acip_transport_t *transport, packet_type_t packet_type, const uint8_t *payload, size_t payload_len)
asciichat_error_t crypto_handshake_client_complete_socket(crypto_handshake_context_t *ctx, socket_t client_socket)
Legacy wrapper: Complete handshake using socket (TCP clients only)
asciichat_error_t crypto_handshake_client_key_exchange(crypto_handshake_context_t *ctx, acip_transport_t *transport, packet_type_t packet_type, const uint8_t *payload, size_t payload_len)
asciichat_error_t crypto_handshake_client_auth_response_socket(crypto_handshake_context_t *ctx, socket_t client_socket)
Legacy wrapper: Auth response using socket (TCP clients only)
asciichat_error_t crypto_handshake_client_auth_response(crypto_handshake_context_t *ctx, acip_transport_t *transport, packet_type_t packet_type, const uint8_t *payload, size_t payload_len)
asciichat_error_t crypto_handshake_client_key_exchange_socket(crypto_handshake_context_t *ctx, socket_t client_socket)
Legacy wrapper: Key exchange using socket (TCP clients only)
asciichat_error_t prompt_password(const char *prompt_text, char *password_out, size_t password_max_len)
int receive_packet(socket_t sockfd, packet_type_t *type, void **data, size_t *len)
Receive a basic packet without encryption.
Definition packet.c:766
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)
Definition send.c:41
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)
Definition ssh_keys.c:1127
asciichat_error_t ed25519_sign_message(const private_key_t *key, const uint8_t *message, size_t message_len, uint8_t signature[64])
Definition ssh_keys.c:1031
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:456
acip_transport_t * acip_tcp_transport_create(socket_t sockfd, crypto_context_t *crypto_ctx)
void acip_transport_destroy(acip_transport_t *transport)
const char * platform_getenv(const char *name)
Definition wasm/system.c:13