ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
lib/crypto/handshake/client.c
Go to the documentation of this file.
1
10#include <ascii-chat/common.h>
12#include <ascii-chat/util/ip.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
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",
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 =
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 =
82 if (validation_result != ASCIICHAT_OK) {
83 SAFE_FREE(server_ephemeral_key);
84 SAFE_FREE(server_identity_key);
85 SAFE_FREE(server_signature);
86 return validation_result;
87 }
88
89 // Check if server is using authenticated format (includes signature)
90 // Must have signature_size > 0 to distinguish from simple ephemeral-only format
91 // This covers both cases:
92 // 1. With identity key: ephemeral + identity + signature
93 // 2. Without identity key but with signature: ephemeral + 0 + signature
94 if (ctx->crypto_ctx.signature_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,
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 SAFE_FREE(server_ephemeral_key);
153 SAFE_FREE(server_identity_key);
154 SAFE_FREE(server_signature);
155 return SET_ERRNO(ERROR_CRYPTO, "Server signature verification FAILED - rejecting connection. "
156 "This indicates: Server's identity key does not "
157 "match its ephemeral key, Potential man-in-the-middle attack, "
158 "Corrupted or malicious server");
159 }
160 log_debug("Server signature verified successfully");
161 }
162
163 // Verify server identity against expected key if --server-key is specified
164 if (ctx->verify_server_key && strlen(ctx->expected_server_key) > 0) {
165 // Parse ALL expected server keys (github:/gitlab: may have multiple keys)
166 public_key_t expected_keys[MAX_CLIENTS];
167 size_t num_expected_keys = 0;
168 if (parse_public_keys(ctx->expected_server_key, expected_keys, &num_expected_keys, MAX_CLIENTS) != 0 ||
169 num_expected_keys == 0) {
170 SAFE_FREE(server_ephemeral_key);
171 SAFE_FREE(server_identity_key);
172 SAFE_FREE(server_signature);
173 return SET_ERRNO(ERROR_CONFIG,
174 "Failed to parse expected server key: %s. Check that "
175 "--server-key value is valid (ssh-ed25519 "
176 "format, github:username, or hex)",
178 }
179
180 // Compare server's IDENTITY key against ALL expected keys (match any one)
181 // This supports users with multiple SSH keys (e.g., different machines)
182 bool key_matched = false;
183 for (size_t i = 0; i < num_expected_keys; i++) {
184 if (sodium_memcmp(server_identity_key, expected_keys[i].key, ED25519_PUBLIC_KEY_SIZE) == 0) {
185 key_matched = true;
186 log_debug("Server identity key matched expected key %zu/%zu", i + 1, num_expected_keys);
187 break;
188 }
189 }
190
191 if (!key_matched) {
192 SAFE_FREE(server_ephemeral_key);
193 SAFE_FREE(server_identity_key);
194 SAFE_FREE(server_signature);
195 return SET_ERRNO(ERROR_CRYPTO,
196 "Server identity key mismatch - potential MITM attack! "
197 "Expected key(s) from: %s (checked %zu keys), Server presented a different key "
198 "than specified with --server-key, DO NOT CONNECT to this "
199 "server - likely man-in-the-middle attack!",
200 ctx->expected_server_key, num_expected_keys);
201 }
202 log_info("Server identity key verified against --server-key (%zu key(s) checked)", num_expected_keys);
203 }
204
205 // Note: Server IP resolution now handled by caller (TCP transport layer)
206 // For WebSocket/WebRTC transports, server_ip should be set by the transport
207 // before handshake begins. TCP clients will use the legacy wrapper which
208 // handles this.
209 if (ctx->server_ip[0] == '\0') {
210 log_debug("Server IP not set - skipping known_hosts verification (non-TCP transport)");
211 } else {
212 log_debug("Server IP already set: %s", ctx->server_ip);
213 }
214
215 // Check known_hosts for this server (if we have server IP and port)
216 // Check if known_hosts verification should be skipped
217 bool skip_known_hosts = false;
218 const char *env_skip = platform_getenv("ASCII_CHAT_INSECURE_NO_HOST_IDENTITY_CHECK");
219 if (env_skip && strcmp(env_skip, STR_ONE) == 0) {
220 log_warn(
221 "Skipping known_hosts checking for authenticated connection (ASCII_CHAT_INSECURE_NO_HOST_IDENTITY_CHECK=1)");
222 skip_known_hosts = true;
223 }
224#ifndef NDEBUG
225 // In debug builds, also skip for Claude Code (LLM automation can't do interactive prompts)
226 else if (platform_getenv("CLAUDECODE")) {
227 log_warn("Skipping known_hosts checking (CLAUDECODE set in debug build)");
228 skip_known_hosts = true;
229 }
230#endif
231
232 if (!skip_known_hosts && ctx->server_ip[0] != '\0' && ctx->server_port > 0) {
233 asciichat_error_t known_host_result = check_known_host(ctx->server_ip, ctx->server_port, server_identity_key);
234 if (known_host_result == ERROR_CRYPTO_VERIFICATION) {
235 // Key mismatch - MITM attack detected! Prompt user for confirmation
236 log_error("SECURITY: Server key does NOT match known_hosts entry!\n"
237 "This indicates a possible man-in-the-middle attack!");
238 uint8_t stored_key[ZERO_KEY_SIZE] = {0}; // We don't have the stored key easily
239 // accessible, use zeros for now
240 if (!display_mitm_warning(ctx->server_ip, ctx->server_port, stored_key, server_identity_key)) {
241 // User declined to continue - ABORT connection for security SAFE_FREE(server_ephemeral_key);
242 SAFE_FREE(server_identity_key);
243 SAFE_FREE(server_signature);
245 "SECURITY: Connection aborted - server key mismatch (possible MITM attack)");
246 }
247 // User accepted the risk - continue with connection
248 log_warn("SECURITY WARNING: User accepted MITM risk - continuing with connection");
249 } else if (known_host_result == ASCIICHAT_OK) {
250 // Unknown host (first connection) - prompt user to verify fingerprint
251 if (!prompt_unknown_host(ctx->server_ip, ctx->server_port, server_identity_key)) {
252 // User declined to add host - ABORT connection SAFE_FREE(server_ephemeral_key);
253 SAFE_FREE(server_identity_key);
254 SAFE_FREE(server_signature);
255 return SET_ERRNO(ERROR_CRYPTO, "User declined to verify unknown host");
256 }
257
258 // User accepted - add to known_hosts
259 if (add_known_host(ctx->server_ip, ctx->server_port, server_identity_key) != ASCIICHAT_OK) {
260 SAFE_FREE(server_ephemeral_key);
261 SAFE_FREE(server_identity_key);
262 SAFE_FREE(server_signature);
263 return SET_ERRNO(ERROR_CONFIG,
264 "CRITICAL SECURITY ERROR: Failed to create known_hosts "
265 "file! This is a security vulnerability - the "
266 "program cannot track known hosts. Please check file "
267 "permissions and ensure the program can write to: %s",
269 }
270 log_debug("Server host added to known_hosts successfully");
271 } else if (known_host_result == 1) {
272 // Key matches - connection is secure!
273 log_info("Server host key verified from known_hosts - connection secure");
274 } else {
275 // Unexpected error code from check_known_host SAFE_FREE(server_ephemeral_key);
276 SAFE_FREE(server_identity_key);
277 SAFE_FREE(server_signature);
278 return SET_ERRNO(known_host_result, "SECURITY: known_hosts verification failed with error code %d",
279 known_host_result);
280 }
281 }
282 } else if (payload_len == ctx->crypto_ctx.public_key_size) {
283 // Simple format: just ephemeral key (no identity key)
284 log_debug("Received simple KEY_EXCHANGE_INIT (%zu bytes) - server has no "
285 "identity key",
286 payload_len);
287 memcpy(server_ephemeral_key, payload, ctx->crypto_ctx.public_key_size);
288
289 // Clear identity key and signature for simple format
290 memset(server_identity_key, 0, ctx->crypto_ctx.auth_public_key_size);
291 memset(server_signature, 0, ctx->crypto_ctx.signature_size);
292
293 // Server is not using client authentication in simple mode
294 ctx->server_uses_client_auth = false;
295
296 log_debug("Received ephemeral key (simple format)");
297
298 // SECURITY: For servers without identity keys, we implement a different security model:
299 // 1. Verify IP address matches known_hosts entry
300 // 2. Always require user confirmation (no silent connections)
301 // 3. Store server fingerprint for future verification
302
303 if (ctx->server_ip[0] == '\0' || ctx->server_port <= 0) {
304 SAFE_FREE(server_ephemeral_key);
305 SAFE_FREE(server_identity_key);
306 SAFE_FREE(server_signature);
307 return SET_ERRNO(ERROR_CRYPTO, "Server IP or port not set, cannot check known_hosts");
308 }
309
310 // Check if this server was previously connected to (IP verification)
311 bool skip_known_hosts = false;
312 asciichat_error_t known_host_result = ASCIICHAT_OK;
313 const char *env_skip_known_hosts_checking = platform_getenv("ASCII_CHAT_INSECURE_NO_HOST_IDENTITY_CHECK");
314 if (env_skip_known_hosts_checking && strcmp(env_skip_known_hosts_checking, STR_ONE) == 0) {
315 log_warn("Skipping known_hosts checking. This is a security vulnerability.");
316 skip_known_hosts = true;
317 }
318#ifndef NDEBUG
319 // In debug builds, also skip for Claude Code (LLM automation can't do interactive prompts)
320 else if (platform_getenv("CLAUDECODE")) {
321 log_warn("Skipping known_hosts checking (CLAUDECODE set in debug build).");
322 skip_known_hosts = true;
323 }
324#endif
325 else {
326 known_host_result = check_known_host_no_identity(ctx->server_ip, ctx->server_port);
327 }
328
329 if (skip_known_hosts || known_host_result == 1) {
330 // Server IP is known and verified - allow connection without warnings
331 log_info("SECURITY: Server IP %s:%u is known (no-identity entry found) - connection verified", ctx->server_ip,
332 ctx->server_port);
333 } else if (known_host_result == ASCIICHAT_OK) {
334 // Server IP is unknown - require user confirmation
335 log_warn("SECURITY: Unknown server IP %s:%u with no identity key\n"
336 "This connection is vulnerable to man-in-the-middle attacks\n"
337 "Anyone can intercept your connection and read your data",
338 ctx->server_ip, ctx->server_port);
339
341 SAFE_FREE(server_ephemeral_key);
342 SAFE_FREE(server_identity_key);
343 SAFE_FREE(server_signature);
344 return SET_ERRNO(ERROR_CRYPTO, "User declined to connect to unknown server without identity key");
345 }
346
347 // User accepted - add to known_hosts as no-identity entry
348 // For servers without identity keys, pass zero key to indicate no-identity
349 uint8_t zero_key[ZERO_KEY_SIZE] = {0};
350 if (add_known_host(ctx->server_ip, ctx->server_port, zero_key) != ASCIICHAT_OK) {
351 SAFE_FREE(server_ephemeral_key);
352 SAFE_FREE(server_identity_key);
353 SAFE_FREE(server_signature);
354 return SET_ERRNO(ERROR_CONFIG,
355 "CRITICAL SECURITY ERROR: Failed to create known_hosts "
356 "file! This is a security vulnerability - the "
357 "program cannot track known hosts. Please check file "
358 "permissions and ensure the program can write to: %s",
360 }
361 log_debug("Server host added to known_hosts successfully");
362 } else if (known_host_result == ERROR_CRYPTO_VERIFICATION) {
363 // Server previously had identity key but now has none - potential security issue
364 log_warn("SECURITY: Server previously had identity key but now has none - potential security issue");
365 SAFE_FREE(server_ephemeral_key);
366 SAFE_FREE(server_identity_key);
367 SAFE_FREE(server_signature);
368 return SET_ERRNO(ERROR_CRYPTO_VERIFICATION, "Server key configuration changed - potential security issue");
369 } else {
370 // Other error checking known_hosts (e.g., ERROR_INVALID_PARAM) SAFE_FREE(server_ephemeral_key);
371 SAFE_FREE(server_identity_key);
372 SAFE_FREE(server_signature);
373 return SET_ERRNO(ERROR_CRYPTO, "Failed to verify server IP address");
374 }
375 } else {
376 SAFE_FREE(server_ephemeral_key);
377 SAFE_FREE(server_identity_key);
378 SAFE_FREE(server_signature);
380 "Invalid KEY_EXCHANGE_INIT size: %zu bytes (expected %zu or "
381 "%zu). This indicates: Protocol violation "
382 "or incompatible server version, Potential man-in-the-middle "
383 "attack, Network corruption",
384 payload_len, expected_auth_size, ctx->crypto_ctx.public_key_size);
385 // retry
386 }
387
388 // Set peer's public key (EPHEMERAL X25519) - this also derives the shared secret
389 crypto_result_t crypto_result = crypto_set_peer_public_key(&ctx->crypto_ctx, server_ephemeral_key);
390 if (crypto_result != CRYPTO_OK) {
391 SAFE_FREE(server_ephemeral_key);
392 SAFE_FREE(server_identity_key);
393 SAFE_FREE(server_signature);
394 return SET_ERRNO(ERROR_CRYPTO, "Failed to set peer public key and derive shared secret: %s",
395 crypto_result_to_string(crypto_result));
396 }
397
398 // Determine if client has an identity key
399 bool client_has_identity_key = (ctx->client_private_key.type == KEY_TYPE_ED25519);
400
401 // Send authenticated response if server has identity key (auth_public_key_size > 0)
402 // OR if server requires client authentication (require_client_auth)
403 // Note: server_uses_client_auth is set when server has identity key, but we should
404 // send authenticated response when server has identity key regardless of client auth requirement
405 bool server_has_identity = (ctx->crypto_ctx.auth_public_key_size > 0 && ctx->crypto_ctx.signature_size > 0);
406 bool server_requires_auth = server_has_identity || ctx->require_client_auth;
407
408 if (server_requires_auth) {
409 // Send authenticated packet:
410 // [ephemeral:kex_size][identity:auth_size][signature:sig_size][gpg_key_id_len:1][gpg_key_id:0-16]
411 // Use Ed25519 sizes since client has Ed25519 key
412 size_t ed25519_pubkey_size = ED25519_PUBLIC_KEY_SIZE; // Ed25519 public key is always 32 bytes
413 size_t ed25519_sig_size = ED25519_SIGNATURE_SIZE; // Ed25519 signature is always 64 bytes
414 size_t response_size = ctx->crypto_ctx.public_key_size + ed25519_pubkey_size + ed25519_sig_size;
415
416 // Check if client has a GPG key ID to send
417 uint8_t gpg_key_id_len = 0;
418 if (ctx->client_gpg_key_id[0] != '\0') {
419 gpg_key_id_len = (uint8_t)strlen(ctx->client_gpg_key_id);
420 if (gpg_key_id_len > 40) {
421 gpg_key_id_len = 40; // Truncate to max length (full fingerprint)
422 }
423 response_size += 1 + gpg_key_id_len; // 1 byte for length + key ID
424 } else {
425 response_size += 1; // Just the length byte (0)
426 }
427
428 uint8_t *key_response = SAFE_MALLOC(response_size, uint8_t *);
429 size_t offset = 0;
430
431 // Copy ephemeral key
432 memcpy(key_response + offset, ctx->crypto_ctx.public_key,
433 ctx->crypto_ctx.public_key_size); // X25519 ephemeral for encryption
434 offset += ctx->crypto_ctx.public_key_size;
435
436 if (client_has_identity_key) {
437 // Client has identity key - send it with signature
438 memcpy(key_response + offset, ctx->client_private_key.public_key, ed25519_pubkey_size); // Ed25519 identity
439 offset += ed25519_pubkey_size;
440
441 // Sign ephemeral key with client identity key
443 key_response + offset) != 0) {
444 SAFE_FREE(key_response);
445 SAFE_FREE(server_ephemeral_key);
446 SAFE_FREE(server_identity_key);
447 SAFE_FREE(server_signature);
448 return SET_ERRNO(ERROR_CRYPTO, "Failed to sign client ephemeral key");
449 }
450 offset += ed25519_sig_size;
451 } else {
452 // Client has no identity key - send null identity and null signature
453 memset(key_response + offset, 0, ed25519_pubkey_size); // Null identity
454 offset += ed25519_pubkey_size;
455 memset(key_response + offset, 0, ed25519_sig_size); // Null signature
456 offset += ed25519_sig_size;
457 }
458
459 // Append GPG key ID length
460 key_response[offset] = gpg_key_id_len;
461 offset += 1;
462
463 // Append GPG key ID if present
464 if (gpg_key_id_len > 0) {
465 memcpy(key_response + offset, ctx->client_gpg_key_id, gpg_key_id_len);
466 offset += gpg_key_id_len;
467 log_debug("Including client GPG key ID in KEY_EXCHANGE_RESPONSE: %.*s", gpg_key_id_len, ctx->client_gpg_key_id);
468 }
469
470 result = packet_send_via_transport(transport, PACKET_TYPE_CRYPTO_KEY_EXCHANGE_RESP, key_response, response_size, 0);
471 if (result != 0) {
472 SAFE_FREE(key_response);
473 SAFE_FREE(server_ephemeral_key);
474 SAFE_FREE(server_identity_key);
475 SAFE_FREE(server_signature);
476 return SET_ERRNO(ERROR_NETWORK, "Failed to send KEY_EXCHANGE_RESPONSE packet");
477 }
478
479 // Zero out the buffer before freeing
480 sodium_memzero(key_response, response_size);
481 SAFE_FREE(key_response);
482 } else {
483 // Send X25519 encryption key only to server (no identity key)
484 // Format: [X25519 pubkey (kex_size)] = kex_size bytes total
487 if (result != 0) {
488 SAFE_FREE(server_ephemeral_key);
489 SAFE_FREE(server_identity_key);
490 SAFE_FREE(server_signature);
491 return SET_ERRNO(ERROR_NETWORK, "Failed to send KEY_EXCHANGE_RESPONSE packet");
492 }
493 }
494
496
497 // Free temporary buffers before successful return
498 SAFE_FREE(server_ephemeral_key);
499 SAFE_FREE(server_identity_key);
500 SAFE_FREE(server_signature);
501
502 return ASCIICHAT_OK;
503}
504// Helper: Send password-based authentication response with mutual auth
505static asciichat_error_t send_password_auth_response(crypto_handshake_context_t *ctx, acip_transport_t *transport,
506 const uint8_t *nonce, const char *auth_context) {
507 // Ensure shared secret is derived before computing password HMAC
508 // This is critical for password HMAC computation which binds to the shared secret
510 return SET_ERRNO(ERROR_CRYPTO, "Failed to compute password HMAC - key exchange not complete");
511 }
512
513 // Compute HMAC bound to shared_secret (MITM protection)
514 uint8_t hmac_response[HMAC_SHA256_SIZE]; // Maximum size buffer
515 crypto_result_t crypto_result = crypto_compute_auth_response(&ctx->crypto_ctx, nonce, hmac_response);
516 if (crypto_result != CRYPTO_OK) {
517 return SET_ERRNO(ERROR_CRYPTO, "Failed to compute HMAC response: %s", crypto_result_to_string(crypto_result));
518 }
519
520 // Generate client challenge nonce for mutual authentication
521 crypto_result = crypto_generate_nonce(ctx->client_challenge_nonce);
522 if (crypto_result != CRYPTO_OK) {
523 return SET_ERRNO(ERROR_CRYPTO, "Failed to generate client challenge nonce: %s",
524 crypto_result_to_string(crypto_result));
525 }
526
527 // Combine HMAC + client nonce (hmac_size + auth_challenge_size bytes)
528 // Use ctx->crypto_ctx.hmac_size and ctx->crypto_ctx.auth_challenge_size (negotiated during handshake)
529 size_t auth_packet_size = ctx->crypto_ctx.hmac_size + ctx->crypto_ctx.auth_challenge_size;
530 uint8_t auth_packet[HMAC_SHA256_SIZE + HMAC_SHA256_SIZE]; // Maximum size buffer (hmac + challenge)
531 memcpy(auth_packet, hmac_response, ctx->crypto_ctx.hmac_size);
532 memcpy(auth_packet + ctx->crypto_ctx.hmac_size, ctx->client_challenge_nonce, ctx->crypto_ctx.auth_challenge_size);
533
534 log_debug("Sending AUTH_RESPONSE packet with HMAC + client nonce (%zu bytes) - %s", auth_packet_size, auth_context);
535 int result = packet_send_via_transport(transport, PACKET_TYPE_CRYPTO_AUTH_RESPONSE, auth_packet, auth_packet_size, 0);
536 if (result != 0) {
537 return SET_ERRNO(ERROR_NETWORK, "Failed to send AUTH_RESPONSE packet");
538 }
539
540 return ASCIICHAT_OK;
541}
542
543// Helper: Send Ed25519 signature-based authentication response with mutual auth
544static asciichat_error_t send_key_auth_response(crypto_handshake_context_t *ctx, acip_transport_t *transport,
545 const uint8_t *nonce, const char *auth_context) {
546 // Sign the challenge with our Ed25519 private key
547 uint8_t signature[ED25519_SIGNATURE_SIZE]; // Maximum size buffer
548 asciichat_error_t sign_result =
550 if (sign_result != ASCIICHAT_OK) {
551 return SET_ERRNO(ERROR_CRYPTO, "Failed to sign challenge with Ed25519 key");
552 }
553
554 // Generate client challenge nonce for mutual authentication
556 if (crypto_result != CRYPTO_OK) {
557 sodium_memzero(signature, sizeof(signature));
558 return SET_ERRNO(ERROR_CRYPTO, "Failed to generate client challenge nonce: %s",
559 crypto_result_to_string(crypto_result));
560 }
561
562 // Combine signature + client nonce + optional GPG key ID
563 // Packet format: [signature:64][nonce:32][gpg_key_id_len:1][gpg_key_id:0-40]
564 size_t auth_packet_size = ctx->crypto_ctx.signature_size + ctx->crypto_ctx.auth_challenge_size;
565
566 // Check if client has a GPG key ID to send
567 uint8_t gpg_key_id_len = 0;
568 if (ctx->client_gpg_key_id[0] != '\0') {
569 gpg_key_id_len = (uint8_t)strlen(ctx->client_gpg_key_id);
570 if (gpg_key_id_len > 40) {
571 gpg_key_id_len = 40; // Truncate to max length (full fingerprint)
572 }
573 auth_packet_size += 1 + gpg_key_id_len; // 1 byte for length + key ID
574 } else {
575 auth_packet_size += 1; // Just the length byte (0)
576 }
577
579 41]; // Maximum size buffer (signature + challenge + len + key_id[40])
580 size_t offset = 0;
581
582 // Copy signature
583 memcpy(auth_packet + offset, signature, ctx->crypto_ctx.signature_size);
584 offset += ctx->crypto_ctx.signature_size;
585
586 // Copy client nonce
587 memcpy(auth_packet + offset, ctx->client_challenge_nonce, ctx->crypto_ctx.auth_challenge_size);
588 offset += ctx->crypto_ctx.auth_challenge_size;
589
590 // Copy GPG key ID length
591 auth_packet[offset] = gpg_key_id_len;
592 offset += 1;
593
594 // Copy GPG key ID if present
595 if (gpg_key_id_len > 0) {
596 memcpy(auth_packet + offset, ctx->client_gpg_key_id, gpg_key_id_len);
597 offset += gpg_key_id_len;
598 log_debug("Including client GPG key ID in AUTH_RESPONSE: %.*s", gpg_key_id_len, ctx->client_gpg_key_id);
599 }
600
601 sodium_memzero(signature, sizeof(signature));
602
603 log_debug("Sending AUTH_RESPONSE packet with Ed25519 signature + client "
604 "nonce + GPG key ID (%zu bytes) - %s",
605 auth_packet_size, auth_context);
606 int result = packet_send_via_transport(transport, PACKET_TYPE_CRYPTO_AUTH_RESPONSE, auth_packet, auth_packet_size, 0);
607 if (result != 0) {
608 return SET_ERRNO(ERROR_NETWORK, "Failed to send AUTH_RESPONSE packet");
609 }
610
611 return ASCIICHAT_OK;
612}
613// Client: Process auth challenge and send response
615 packet_type_t packet_type, const uint8_t *payload,
616 size_t payload_len) {
617 if (!ctx || ctx->state != CRYPTO_HANDSHAKE_KEY_EXCHANGE) {
618 return SET_ERRNO(ERROR_INVALID_STATE, "Invalid state: ctx=%p, state=%d", ctx, ctx ? ctx->state : -1);
619 }
620 if (!transport) {
621 return SET_ERRNO(ERROR_INVALID_PARAM, "transport is NULL");
622 }
623
624 // Note: Packet already received by ACIP handler
625 int result;
626
627 // If server sent HANDSHAKE_COMPLETE, authentication was skipped (client has
628 // no key)
629 if (packet_type == PACKET_TYPE_CRYPTO_HANDSHAKE_COMPLETE) {
631 ctx->crypto_ctx.handshake_complete = true; // Mark crypto context as ready for rekeying
632 log_debug("Crypto handshake completed successfully (no authentication required)");
633 return ASCIICHAT_OK;
634 }
635
636 // If server sent AUTH_FAILED, client is not authorized
637 if (packet_type == PACKET_TYPE_CRYPTO_AUTH_FAILED) {
638 return SET_ERRNO(ERROR_CRYPTO, "Server rejected authentication - client key not authorized");
639 }
640
641 // Otherwise, verify packet type is AUTH_CHALLENGE
642 if (packet_type != PACKET_TYPE_CRYPTO_AUTH_CHALLENGE) {
644 "Expected AUTH_CHALLENGE, HANDSHAKE_COMPLETE, or AUTH_FAILED, "
645 "got packet type %d",
646 packet_type);
647 }
648
649 // Validate packet size using session parameters
650 asciichat_error_t validation_result =
652 if (validation_result != ASCIICHAT_OK) {
653 return validation_result;
654 }
655
656 // Parse auth requirement flags
657 uint8_t auth_flags = payload[0];
658
659 // Copy nonce to local buffer before freeing payload
660 // Use auth_challenge_size since that's what the server sent
661 // Note: auth_challenge_size is uint8_t (max 255), buffer is 256 bytes, so always sufficient
662 uint8_t nonce_buffer[256];
663 memcpy(nonce_buffer, payload + 1, ctx->crypto_ctx.auth_challenge_size);
664 const uint8_t *nonce = nonce_buffer;
665
666 log_debug("Server auth requirements: password=%s, client_key=%s",
667 (auth_flags & AUTH_REQUIRE_PASSWORD) ? "required" : "no",
668 (auth_flags & AUTH_REQUIRE_CLIENT_KEY) ? "required" : "no");
669
670 // Check if we can satisfy the server's authentication requirements
671 bool has_password = ctx->crypto_ctx.has_password;
672 bool has_client_key = (ctx->client_private_key.type == KEY_TYPE_ED25519);
673 bool password_required = (auth_flags & AUTH_REQUIRE_PASSWORD);
674 bool client_key_required = (auth_flags & AUTH_REQUIRE_CLIENT_KEY);
675
676 // Provide specific error messages based on what's missing
677 if (password_required && !has_password) {
678 if (client_key_required && !has_client_key) {
679 return SET_ERRNO(ERROR_CRYPTO, "Server requires both password and client key authentication. Please "
680 "provide --password and --key to authenticate");
681 }
682 // Prompt for password interactively
683 char prompted_password[PASSWORD_BUFFER_SIZE];
684 if (prompt_password("Server password required - please enter password:", prompted_password,
685 sizeof(prompted_password)) != 0) {
686 return SET_ERRNO(ERROR_CRYPTO, "Failed to read password");
687 }
688
689 // Derive password key from prompted password
690 log_debug("Deriving key from prompted password");
691 crypto_result_t crypto_result = crypto_derive_password_key(&ctx->crypto_ctx, prompted_password);
692 sodium_memzero(prompted_password, sizeof(prompted_password));
693
694 if (crypto_result != CRYPTO_OK) {
695 return SET_ERRNO(ERROR_CRYPTO, "Failed to derive password key: %s", crypto_result_to_string(crypto_result));
696 }
697
698 // Mark that password auth is now available
699 ctx->crypto_ctx.has_password = true;
700 has_password = true; // Update flag for logic below
701 }
702
703 // Authentication response priority:
704 // NOTE: Identity verification happens during KEY_EXCHANGE phase, not
705 // AUTH_RESPONSE!
706 // 1. If server requires password → MUST send HMAC (hmac_size bytes), error if no password
707 // 2. Else if server requires identity (whitelist) → MUST send Ed25519 signature (signature_size bytes), error if no
708 // key
709 // 3. Else if client has password → send HMAC (optional password auth)
710 // 4. Else if client has SSH key → send Ed25519 signature (optional identity
711 // proof)
712 // 5. Else → no authentication available
713
714 // Clean up payload before any early returns
715 if (password_required) {
716 // Server requires password - HIGHEST PRIORITY
717 // (Identity was already verified in KEY_EXCHANGE phase if whitelist is
718 // enabled)
719 if (!has_password) {
720 return SET_ERRNO(ERROR_CRYPTO, "Server requires password authentication\n"
721 "Please provide --password for this server");
722 }
723
724 result = send_password_auth_response(ctx, transport, nonce, "required password");
725 if (result != ASCIICHAT_OK) {
726 SET_ERRNO(ERROR_NETWORK, "Failed to send password auth response");
727 return result;
728 }
729 } else if (client_key_required) {
730 // Server requires client key (whitelist) - SECOND PRIORITY
731 if (!has_client_key) {
732 return SET_ERRNO(ERROR_CRYPTO, "Server requires client key authentication (whitelist)\n"
733 "Please provide --key with your authorized Ed25519 key");
734 }
735
736 result = send_key_auth_response(ctx, transport, nonce, "required client key");
737 if (result != ASCIICHAT_OK) {
738 SET_ERRNO(ERROR_NETWORK, "Failed to send key auth response");
739 return result;
740 }
741 } else if (has_password) {
742 // No server requirements, but client has password → send HMAC + client
743 // nonce (optional)
744 result = send_password_auth_response(ctx, transport, nonce, "optional password");
745 if (result != ASCIICHAT_OK) {
746 SET_ERRNO(ERROR_NETWORK, "Failed to send password auth response");
747 return result;
748 }
749 } else if (has_client_key) {
750 // No server requirements, but client has SSH key → send Ed25519 signature +
751 // client nonce (optional)
752 result = send_key_auth_response(ctx, transport, nonce, "optional identity");
753 if (result != ASCIICHAT_OK) {
754 SET_ERRNO(ERROR_NETWORK, "Failed to send key auth response");
755 return result;
756 }
757 } else {
758 // No authentication method available
759 // Continue without authentication (server will decide if this is
760 // acceptable)
761 log_debug("No authentication credentials provided - continuing without "
762 "authentication");
763 }
764
766
767 return ASCIICHAT_OK;
768}
769// Client: Wait for handshake complete confirmation
771 packet_type_t packet_type, const uint8_t *payload,
772 size_t payload_len) {
773 // Accept both KEY_EXCHANGE and AUTHENTICATING states for simple mode compatibility
774 // In simple mode, server skips AUTH_CHALLENGE and sends HANDSHAKE_COMPLETE directly
776 SET_ERRNO(ERROR_INVALID_STATE, "Invalid state: ctx=%p, state=%d", ctx, ctx ? ctx->state : -1);
777 return ERROR_INVALID_STATE;
778 }
779 if (!transport) {
780 return SET_ERRNO(ERROR_INVALID_PARAM, "transport is NULL");
781 }
782
783 // Note: Packet already received by ACIP handler
784 (void)transport; // Unused parameter (this function only receives, doesn't send)
785
786 // Check packet type
787 if (packet_type == PACKET_TYPE_CRYPTO_AUTH_FAILED) {
788 // Parse the auth failure packet to get specific reasons
789 if (payload_len >= sizeof(auth_failure_packet_t)) {
790 auth_failure_packet_t *failure = (auth_failure_packet_t *)payload;
791 SET_ERRNO(ERROR_CRYPTO_AUTH, "Server rejected authentication:");
792
794 SET_ERRNO(ERROR_CRYPTO_AUTH, " - Incorrect password");
795 }
797 SET_ERRNO(ERROR_CRYPTO_AUTH, " - Server requires a password (use --password)");
798 }
800 SET_ERRNO(ERROR_CRYPTO_AUTH, " - Server requires a whitelisted client key (use --key "
801 "with your SSH key)");
802 }
804 SET_ERRNO(ERROR_CRYPTO_AUTH, " - Your client key is not in the server's whitelist");
805 }
807 SET_ERRNO(ERROR_CRYPTO_AUTH, " - Client signature verification failed");
808 }
809
810 // Provide helpful guidance
814 SET_ERRNO(ERROR_CRYPTO_AUTH, "Hint: Server requires BOTH correct password AND "
815 "whitelisted key");
816 } else if (failure->reason_flags & AUTH_FAIL_PASSWORD_INCORRECT) {
817 SET_ERRNO(ERROR_CRYPTO_AUTH, "Hint: Check your password and try again");
818 } else if (failure->reason_flags & AUTH_FAIL_CLIENT_KEY_REQUIRED) {
819 SET_ERRNO(ERROR_CRYPTO_AUTH, "Hint: Provide your SSH key with --key ~/.ssh/id_ed25519");
820 } else if (failure->reason_flags & AUTH_FAIL_CLIENT_KEY_REJECTED) {
821 SET_ERRNO(ERROR_CRYPTO_AUTH, "Hint: Your key needs to be added to the server's whitelist");
822 }
823 }
824 } else {
825 SET_ERRNO(ERROR_CRYPTO_AUTH, "Server rejected authentication (no details provided)");
826 }
828 "Server authentication failed - incorrect HMAC"); // Special code for
829 // auth failure - do
830 // not retry
831 }
832
833 // Handle no-auth flow: server sends HANDSHAKE_COMPLETE directly
834 if (packet_type == PACKET_TYPE_CRYPTO_HANDSHAKE_COMPLETE) {
836 log_info("Handshake complete (no authentication required)");
837 return ASCIICHAT_OK;
838 }
839
840 // Handle with-auth flow: server sends SERVER_AUTH_RESP after authentication
841 if (packet_type != PACKET_TYPE_CRYPTO_SERVER_AUTH_RESP) {
843 "Expected HANDSHAKE_COMPLETE, SERVER_AUTH_RESPONSE, or AUTH_FAILED, got packet type %d",
844 packet_type);
845 }
846
847 // Verify server's HMAC for mutual authentication
848 // Use ctx->crypto_ctx.hmac_size (negotiated during handshake) rather than SERVER_AUTH_RESPONSE_SIZE constant
849 if (payload_len != ctx->crypto_ctx.hmac_size) {
850 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Invalid SERVER_AUTH_RESPONSE size: %zu bytes (expected %u)", payload_len,
851 ctx->crypto_ctx.hmac_size);
852 }
853
854 // Verify server's HMAC (binds to DH shared_secret to prevent MITM)
856 SET_ERRNO(ERROR_CRYPTO_AUTH, "SECURITY: Server authentication failed - incorrect HMAC");
857 SET_ERRNO(ERROR_CRYPTO_AUTH, "This may indicate a man-in-the-middle attack!");
859 "Server authentication failed - incorrect HMAC"); // Authentication
860 // failure - do not
861 // retry
862 }
864 log_info("Server authentication successful - mutual authentication complete");
865
866 return ASCIICHAT_OK;
867}
⚠️‼️ Comprehensive thread-local error context system for ascii-chat
🗃️ Lock-Free Unified Memory Buffer Pool with Lazy Allocation
⚙️ Common definitions, error codes, macros, and types shared throughout the application
asciichat_error_t crypto_handshake_validate_packet_size(const crypto_handshake_context_t *ctx, uint16_t packet_type, size_t packet_size)
Validate crypto packet size based on session parameters.
#define AUTH_REQUIRE_PASSWORD
Server requires password authentication.
#define AUTH_REQUIRE_CLIENT_KEY
Server requires client key authentication (whitelist)
🔄 Network byte order conversion helpers
GPG operations - main header.
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_MALLOC(size, cast)
Definition common.h:264
unsigned char uint8_t
Definition common.h:56
bool prompt_unknown_host(const char *server_ip, uint16_t port, const uint8_t server_key[32])
Interactive prompt for unknown host - returns true if user wants to add, false to abort.
const char * get_known_hosts_path(void)
Get the path to the known_hosts file.
Definition known_hosts.c:47
crypto_result_t crypto_set_peer_public_key(crypto_context_t *ctx, const uint8_t *peer_public_key)
Set peer's public key and compute shared secret (step 2 of handshake)
#define ED25519_SIGNATURE_SIZE
Ed25519 signature size in bytes.
crypto_result_t crypto_compute_auth_response(const crypto_context_t *ctx, const uint8_t nonce[32], uint8_t hmac_out[32])
Compute authentication response HMAC bound to DH shared_secret.
#define HMAC_SHA256_SIZE
HMAC-SHA256 output size in bytes.
crypto_result_t crypto_generate_nonce(uint8_t nonce[32])
Generate random nonce for authentication.
const char * crypto_result_to_string(crypto_result_t result)
Convert crypto result to human-readable string.
crypto_result_t
Cryptographic operation result codes.
bool prompt_unknown_host_no_identity(const char *server_ip, uint16_t port)
Interactive prompt for unknown host without identity key - returns true if user wants to continue,...
#define ED25519_PUBLIC_KEY_SIZE
Ed25519 public key size in bytes.
asciichat_error_t check_known_host_no_identity(const char *server_ip, uint16_t port)
Check known_hosts for servers without identity key (no-identity entries)
#define HEX_STRING_SIZE_32
Hex string size for 32-byte values (64 hex chars + null terminator)
asciichat_error_t add_known_host(const char *server_ip, uint16_t port, const uint8_t server_key[32])
Add server to known_hosts.
crypto_result_t crypto_derive_password_key(crypto_context_t *ctx, const char *password)
Derive key from password using Argon2id.
#define PASSWORD_BUFFER_SIZE
Password input buffer size (256 bytes)
#define ZERO_KEY_SIZE
Zero key array size (32 bytes, used for no-identity entries)
bool display_mitm_warning(const char *server_ip, uint16_t port, const uint8_t expected_key[32], const uint8_t received_key[32])
Display MITM warning with key comparison and prompt user for confirmation.
asciichat_error_t check_known_host(const char *server_ip, uint16_t port, const uint8_t server_key[32])
Check if server key is in known_hosts.
Definition known_hosts.c:78
bool crypto_verify_auth_response(const crypto_context_t *ctx, const uint8_t nonce[32], const uint8_t expected_hmac[32])
Verify authentication response HMAC bound to DH shared_secret.
#define HEX_STRING_SIZE_64
Hex string size for 64-byte values (128 hex chars + null terminator)
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ERROR_INVALID_STATE
@ ERROR_NETWORK
Definition error_codes.h:77
@ ERROR_CRYPTO_VERIFICATION
@ ERROR_NETWORK_PROTOCOL
Definition error_codes.h:81
@ ERROR_MEMORY
Definition error_codes.h:56
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_CRYPTO_AUTH
Definition error_codes.h:98
@ ERROR_CONFIG
Definition error_codes.h:57
@ ERROR_CRYPTO
Definition error_codes.h:96
@ ERROR_INVALID_PARAM
@ CRYPTO_HANDSHAKE_AUTHENTICATING
@ CRYPTO_HANDSHAKE_INIT
@ CRYPTO_HANDSHAKE_KEY_EXCHANGE
@ CRYPTO_HANDSHAKE_READY
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)
Verify an Ed25519 signature.
Definition ssh_keys.c:1126
key_type_t type
Definition key_types.h:92
asciichat_error_t parse_public_keys(const char *input, public_key_t *keys_out, size_t *num_keys, size_t max_keys)
Parse all SSH/GPG public keys from any format (returns all keys)
Definition keys.c:344
uint8_t public_key[32]
Definition key_types.h:99
asciichat_error_t ed25519_sign_message(const private_key_t *key, const uint8_t *message, size_t message_len, uint8_t signature[64])
Sign a message with Ed25519 (uses SSH agent if available, otherwise in-memory key)
Definition ssh_keys.c:1030
@ KEY_TYPE_ED25519
Definition key_types.h:52
#define MAX_CLIENTS
Maximum possible clients (static array size) - actual runtime limit set by –max-clients (1-32)
Definition limits.h:26
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
packet_type_t
Network protocol packet type enumeration.
Definition packet.h:286
@ AUTH_FAIL_PASSWORD_INCORRECT
Password verification failed (incorrect password)
Definition packet.h:765
@ AUTH_FAIL_PASSWORD_REQUIRED
Server requires password but client didn't provide one.
Definition packet.h:763
@ AUTH_FAIL_CLIENT_KEY_REQUIRED
Server requires client key but client didn't provide one.
Definition packet.h:767
@ AUTH_FAIL_CLIENT_KEY_REJECTED
Client key not in whitelist (access denied)
Definition packet.h:769
@ AUTH_FAIL_SIGNATURE_INVALID
Client signature verification failed (invalid signature)
Definition packet.h:771
@ PACKET_TYPE_CRYPTO_AUTH_RESPONSE
Client -> Server: {HMAC[32]} (UNENCRYPTED)
Definition packet.h:317
@ PACKET_TYPE_CRYPTO_HANDSHAKE_COMPLETE
Server -> Client: "encryption ready" (UNENCRYPTED)
Definition packet.h:323
@ PACKET_TYPE_CRYPTO_KEY_EXCHANGE_INIT
Server -> Client: {server_pubkey[32]} (UNENCRYPTED)
Definition packet.h:311
@ PACKET_TYPE_CRYPTO_KEY_EXCHANGE_RESP
Client -> Server: {client_pubkey[32]} (UNENCRYPTED)
Definition packet.h:313
@ PACKET_TYPE_CRYPTO_AUTH_FAILED
Server -> Client: "authentication failed" (UNENCRYPTED)
Definition packet.h:319
@ PACKET_TYPE_CRYPTO_SERVER_AUTH_RESP
Server -> Client: {HMAC[32]} server proves knowledge (UNENCRYPTED)
Definition packet.h:321
@ PACKET_TYPE_CRYPTO_AUTH_CHALLENGE
Server -> Client: {nonce[32]} (UNENCRYPTED)
Definition packet.h:315
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:148
const char * platform_getenv(const char *name)
Get an environment variable value.
Definition wasm/system.c:39
#define STR_ONE
String literal: "1" (one)
asciichat_error_t prompt_password(const char *prompt_text, char *password_out, size_t password_max_len)
Prompt the user for a password with secure input.
Client-side handshake functions.
🌍 IP Address Parsing and Formatting Utilities
Known hosts management for MITM attack prevention.
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)
Client: Wait for handshake complete confirmation.
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)
Client: Process server's public key and send our public key.
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)
Client: Process auth challenge and send response.
Password prompting utilities with secure input and formatting.
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
ACIP shared/bidirectional packet sending functions.
Transport instance structure.
Definition transport.h:214
Authentication failure packet structure.
Definition packet.h:782
uint8_t reason_flags
Bitmask of auth_failure_reason_t values indicating failure causes.
Definition packet.h:784
Cryptographic handshake context structure.
crypto_handshake_state_t state
Public key structure.
Definition key_types.h:69
Transport abstraction layer for ACIP protocol.