ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
lib/crypto/handshake/server.c
Go to the documentation of this file.
1
7#include <ascii-chat/crypto/handshake/server.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/crypto/crypto.h>
12#include <ascii-chat/crypto/gpg/gpg.h>
13#include <ascii-chat/network/packet.h>
14#include <ascii-chat/network/acip/transport.h>
15#include <ascii-chat/network/acip/send.h>
16#include <stdio.h>
17#include <string.h>
18
19// Server: Start crypto handshake by sending public key
20asciichat_error_t crypto_handshake_server_start(crypto_handshake_context_t *ctx, acip_transport_t *transport) {
21 log_debug("[HANDSHAKE_START] ===== ENTRY: crypto_handshake_server_start called =====");
22 log_debug("[HANDSHAKE_START] ctx=%p, transport=%p", (void *)ctx, (void *)transport);
23
24 if (!ctx || ctx->state != CRYPTO_HANDSHAKE_INIT) {
25 log_error("[HANDSHAKE_START] FAILED: Invalid state - ctx=%p, state=%d", (void *)ctx, ctx ? (int)ctx->state : -1);
26 return SET_ERRNO(ERROR_INVALID_STATE, "Invalid state: ctx=%p, state=%d", (void *)ctx, ctx ? (int)ctx->state : -1);
27 }
28 log_debug("[HANDSHAKE_START] State check OK: state=%d (CRYPTO_HANDSHAKE_INIT)", ctx->state);
29
30 int result;
31
32 // Calculate packet size based on negotiated crypto parameters
33 size_t expected_packet_size =
34 ctx->crypto_ctx.public_key_size + ctx->crypto_ctx.auth_public_key_size + ctx->crypto_ctx.signature_size;
35
36 log_debug("SERVER_KEY_EXCHANGE: kex_size=%u, auth_size=%u, sig_size=%u, expected_size=%zu",
37 ctx->crypto_ctx.public_key_size, ctx->crypto_ctx.auth_public_key_size, ctx->crypto_ctx.signature_size,
38 expected_packet_size);
39
40 // Check if we have an identity key to send authenticated packet
41 if (ctx->server_private_key.type == KEY_TYPE_ED25519) {
42 // Extended packet format:
43 // [ephemeral_key:kex_size][identity_key:auth_size][signature:sig_size]
44 uint8_t *extended_packet;
45 extended_packet = SAFE_MALLOC(expected_packet_size, uint8_t *);
46 if (!extended_packet) {
47 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate memory for extended packet");
48 }
49
50 // Copy ephemeral public key
51 memcpy(extended_packet, ctx->crypto_ctx.public_key, ctx->crypto_ctx.public_key_size);
52
53 // Copy identity public key
54 memcpy(extended_packet + ctx->crypto_ctx.public_key_size, ctx->server_private_key.public_key,
55 ctx->crypto_ctx.auth_public_key_size);
56
57 // DEBUG: Print identity key being sent
58 char hex[HEX_STRING_SIZE_32];
59 for (int i = 0; i < ED25519_PUBLIC_KEY_SIZE; i++) {
60 safe_snprintf(hex + i * 2, 3, "%02x", ctx->server_private_key.public_key[i]);
61 }
62 hex[HEX_STRING_SIZE_32 - 1] = '\0';
63
64 // Sign the ephemeral key with our identity key
65 log_debug("Signing ephemeral key with server identity key");
66
67 // Log key details in debug mode
68 char hex_ephemeral[65], hex_identity[65];
69 for (int i = 0; i < 32; i++) {
70 safe_snprintf(hex_ephemeral + i * 2, 3, "%02x", ctx->crypto_ctx.public_key[i]);
71 safe_snprintf(hex_identity + i * 2, 3, "%02x", ctx->server_private_key.public_key[i]);
72 }
73 hex_ephemeral[64] = hex_identity[64] = '\0';
74 log_debug("SERVER: Ephemeral key (32 bytes): %s", hex_ephemeral);
75 log_debug("SERVER: Identity public key: %s", hex_identity);
76
77 if (ed25519_sign_message(&ctx->server_private_key, ctx->crypto_ctx.public_key, ctx->crypto_ctx.public_key_size,
78 extended_packet + ctx->crypto_ctx.public_key_size +
79 ctx->crypto_ctx.auth_public_key_size) != 0) {
80 SAFE_FREE(extended_packet);
81 return SET_ERRNO(ERROR_CRYPTO, "Failed to sign ephemeral key with identity key");
82 }
83
84 log_debug("Sending authenticated KEY_EXCHANGE_INIT (%zu bytes: ephemeral + "
85 "identity + signature)",
86 expected_packet_size);
87 log_debug("[HANDSHAKE_START] Calling packet_send_via_transport(type=%d, payload_len=%zu, 0)...",
88 PACKET_TYPE_CRYPTO_KEY_EXCHANGE_INIT, expected_packet_size);
89 result = packet_send_via_transport(transport, PACKET_TYPE_CRYPTO_KEY_EXCHANGE_INIT, extended_packet,
90 expected_packet_size, 0);
91 log_debug("[HANDSHAKE_START] packet_send_via_transport returned %d", result);
92 SAFE_FREE(extended_packet);
93 } else {
94 // No identity key - send just the ephemeral key
95 log_debug("Sending simple KEY_EXCHANGE_INIT (%zu bytes: ephemeral key only)", ctx->crypto_ctx.public_key_size);
96 log_debug("[HANDSHAKE_START] Calling packet_send_via_transport(type=%d, payload_len=%zu, 0)...",
97 PACKET_TYPE_CRYPTO_KEY_EXCHANGE_INIT, ctx->crypto_ctx.public_key_size);
98 result = packet_send_via_transport(transport, PACKET_TYPE_CRYPTO_KEY_EXCHANGE_INIT, ctx->crypto_ctx.public_key,
99 ctx->crypto_ctx.public_key_size, 0);
100 log_debug("[HANDSHAKE_START] packet_send_via_transport returned %d", result);
101 }
102
103 if (result != ASCIICHAT_OK) {
104 log_error("[HANDSHAKE_START] FAILED: packet_send returned %d", result);
105 return SET_ERRNO(ERROR_NETWORK, "Failed to send KEY_EXCHANGE_INIT packet");
106 }
107 log_debug("[HANDSHAKE_START] ===== SUCCESS: KEY_EXCHANGE_INIT sent =====");
108
109 ctx->state = CRYPTO_HANDSHAKE_KEY_EXCHANGE;
110
111 return ASCIICHAT_OK;
112}
113// Server: Process client's public key and send auth challenge
114asciichat_error_t crypto_handshake_server_auth_challenge(crypto_handshake_context_t *ctx, acip_transport_t *transport,
115 packet_type_t packet_type, const uint8_t *payload,
116 size_t payload_len) {
117 if (!ctx || ctx->state != CRYPTO_HANDSHAKE_KEY_EXCHANGE) {
118 return SET_ERRNO(ERROR_INVALID_STATE, "Invalid state: ctx=%p, state=%d", ctx, ctx ? ctx->state : -1);
119 }
120 if (!transport) {
121 return SET_ERRNO(ERROR_INVALID_PARAM, "transport is NULL");
122 }
123
124 // Note: Packet already received by ACIP handler
125 int result;
126
127 // Check if client sent NO_ENCRYPTION response
128 if (packet_type == PACKET_TYPE_CRYPTO_NO_ENCRYPTION) {
129
130 // Send AUTH_FAILED to inform client (though they already know)
131 auth_failure_packet_t failure = {0};
132 failure.reason_flags = 0; // No specific auth failure, just encryption mismatch
133 int send_result =
134 packet_send_via_transport(transport, PACKET_TYPE_CRYPTO_AUTH_FAILED, &failure, sizeof(failure), 0);
135 if (send_result != 0) {
136 return SET_ERRNO(ERROR_NETWORK, "Failed to send AUTH_FAILED packet");
137 }
138
139 return SET_ERRNO(ERROR_CRYPTO, "SECURITY: Client sent NO_ENCRYPTION response - encryption mode "
140 "mismatch. Server requires encryption, but "
141 "client has --no-encrypt. Use matching encryption settings on "
142 "both client and server");
143 }
144
145 // Verify packet type
146 if (packet_type != PACKET_TYPE_CRYPTO_KEY_EXCHANGE_RESP) {
147 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Expected KEY_EXCHANGE_RESPONSE, got packet type %d", packet_type);
148 }
149
150 // Verify payload size - client can send either simple or authenticated format
151 // Simple: kex_public_key_size bytes
152 // Authenticated: public_key_size + client_auth_key_size + client_sig_size bytes
153 size_t simple_size = ctx->crypto_ctx.public_key_size;
154 // Ed25519 public key is always 32 bytes
155 // Ed25519 signature is always 64 bytes
156 size_t authenticated_size = ctx->crypto_ctx.public_key_size + ED25519_PUBLIC_KEY_SIZE + ED25519_SIGNATURE_SIZE;
157
158 bool client_sent_identity = false;
159 uint8_t *client_ephemeral_key = SAFE_MALLOC(ctx->crypto_ctx.public_key_size, uint8_t *);
160 uint8_t *client_identity_key = SAFE_MALLOC(ED25519_PUBLIC_KEY_SIZE, uint8_t *);
161 uint8_t *client_signature = SAFE_MALLOC(ED25519_SIGNATURE_SIZE, uint8_t *);
162
163 if (!client_ephemeral_key || !client_identity_key || !client_signature) {
164 if (client_ephemeral_key)
165 SAFE_FREE(client_ephemeral_key);
166 if (client_identity_key)
167 SAFE_FREE(client_identity_key);
168 if (client_signature)
169 SAFE_FREE(client_signature);
170 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate memory for client keys");
171 }
172
173 // Validate packet size using session parameters
174 asciichat_error_t validation_result =
175 crypto_handshake_validate_packet_size(ctx, PACKET_TYPE_CRYPTO_KEY_EXCHANGE_RESP, payload_len);
176 if (validation_result != ASCIICHAT_OK) {
177 // Payload ownership remains with caller
178 SAFE_FREE(client_ephemeral_key);
179 SAFE_FREE(client_identity_key);
180 SAFE_FREE(client_signature);
181 return validation_result;
182 }
183
184 // Handle both authenticated and non-authenticated responses
185 // authenticated_size is now a minimum - packet may be larger with GPG key ID
186 if (payload_len >= authenticated_size) {
187 // Authenticated format (may have optional GPG key ID):
188 // [ephemeral:kex_size][identity:auth_size][signature:sig_size][gpg_key_id_len:1][gpg_key_id:0-16]
189 memcpy(client_ephemeral_key, payload, ctx->crypto_ctx.public_key_size);
190 memcpy(client_identity_key, payload + ctx->crypto_ctx.public_key_size, ED25519_PUBLIC_KEY_SIZE);
191 memcpy(client_signature, payload + ctx->crypto_ctx.public_key_size + ED25519_PUBLIC_KEY_SIZE,
192 ED25519_SIGNATURE_SIZE);
193 client_sent_identity = true;
194 ctx->client_sent_identity = true;
195
196 // Extract GPG key ID if present
197 const char *client_gpg_key_id = NULL;
198 char gpg_key_id_buffer[41] = {0};
199 size_t gpg_offset = ctx->crypto_ctx.public_key_size + ED25519_PUBLIC_KEY_SIZE + ED25519_SIGNATURE_SIZE;
200 if (payload_len > gpg_offset) {
201 uint8_t gpg_key_id_len = payload[gpg_offset];
202 if (gpg_key_id_len > 0 && gpg_key_id_len <= 40 && payload_len >= gpg_offset + 1 + gpg_key_id_len) {
203 memcpy(gpg_key_id_buffer, payload + gpg_offset + 1, gpg_key_id_len);
204 gpg_key_id_buffer[gpg_key_id_len] = '\0';
205 client_gpg_key_id = gpg_key_id_buffer;
206 log_debug("Extracted client GPG key ID from KEY_EXCHANGE_RESPONSE: %s", client_gpg_key_id);
207 }
208 }
209
210 // Check if client sent a null identity key (all zeros)
211 bool client_has_null_identity = true;
212 for (size_t i = 0; i < ED25519_PUBLIC_KEY_SIZE; i++) {
213 if (client_identity_key[i] != 0) {
214 client_has_null_identity = false;
215 break;
216 }
217 }
218
219 if (client_has_null_identity) {
220 // Client has no identity key - this is allowed for servers without client authentication
221 log_debug("Client sent null identity key - no client authentication required");
222 client_sent_identity = false;
223 ctx->client_sent_identity = false;
224 log_warn("Client connected without identity authentication");
225 } else {
226 // Client has a real identity key
227 // If server didn't specify --client-keys, skip signature verification
228 // (server doesn't care about client identity verification)
229 if (!ctx->require_client_auth) {
230 log_info("Skipping client signature verification (no --client-keys specified)");
231 log_warn("Connection is encrypted but client identity is NOT verified");
232 } else {
233 // Verify client's signature
234 log_debug("Verifying client's signature");
235 // Pass client's GPG key ID if available
236 if (ed25519_verify_signature(client_identity_key, client_ephemeral_key, ctx->crypto_ctx.public_key_size,
237 client_signature, client_gpg_key_id) != 0) {
238 // Send AUTH_FAILED with specific reason
239 auth_failure_packet_t failure = {0};
240 failure.reason_flags = AUTH_FAIL_SIGNATURE_INVALID;
241 int send_result =
242 packet_send_via_transport(transport, PACKET_TYPE_CRYPTO_AUTH_FAILED, &failure, sizeof(failure), 0);
243 if (send_result != 0) {
244 return SET_ERRNO(ERROR_NETWORK, "Failed to send AUTH_FAILED packet");
245 }
246
247 return SET_ERRNO(ERROR_CRYPTO, "Client signature verification FAILED - rejecting connection");
248 }
249 }
250 }
251
252 // Store the verified client identity for whitelist checking (only if client has identity)
253 if (client_sent_identity) {
254 ctx->client_ed25519_key.type = KEY_TYPE_ED25519;
255 memcpy(ctx->client_ed25519_key.key, client_identity_key, ctx->crypto_ctx.public_key_size);
256 }
257 } else if (payload_len == simple_size) {
258 // Non-authenticated format: [ephemeral:public_key_size] only
259 log_debug("Client sent non-authenticated response (%zu bytes)", payload_len);
260 memcpy(client_ephemeral_key, payload, ctx->crypto_ctx.public_key_size);
261 client_sent_identity = false;
262 ctx->client_sent_identity = false;
263 log_warn("Client connected without identity authentication");
264 } else {
265 // Payload ownership remains with caller
266 SAFE_FREE(client_ephemeral_key);
267 SAFE_FREE(client_identity_key);
268 SAFE_FREE(client_signature);
269 return SET_ERRNO(ERROR_NETWORK_PROTOCOL,
270 "Invalid client key response size: %zu bytes (expected %zu for "
271 "authenticated or %zu for simple)",
272 payload_len, authenticated_size, simple_size);
273 }
274
275 // Extract pointers for compatibility with existing code
276 const uint8_t *client_x25519 = client_ephemeral_key;
277 const uint8_t *client_ed25519 = client_sent_identity ? client_identity_key : NULL;
278
279 // Check client Ed25519 key against whitelist if client provided one and
280 // whitelist is enabled
281 if (client_sent_identity && ctx->require_client_auth && ctx->client_whitelist && ctx->num_whitelisted_clients > 0) {
282 bool key_found = false;
283
284 // Debug: print client's Ed25519 identity key
285 char client_ed25519_hex[HEX_STRING_SIZE_32];
286 for (int i = 0; i < ED25519_PUBLIC_KEY_SIZE; i++) {
287 safe_snprintf(client_ed25519_hex + i * 2, 3, "%02x", client_ed25519[i]);
288 }
289 log_debug("Client Ed25519 identity key: %s", client_ed25519_hex);
290
291 // Compare against whitelist (direct Ed25519 comparison - no conversion!)
292 for (size_t i = 0; i < ctx->num_whitelisted_clients; i++) {
293 // Debug: print whitelist Ed25519 key
294 char whitelist_ed25519_hex[HEX_STRING_SIZE_32];
295 for (int j = 0; j < ED25519_PUBLIC_KEY_SIZE; j++) {
296 safe_snprintf(whitelist_ed25519_hex + j * 2, 3, "%02x", ctx->client_whitelist[i].key[j]);
297 }
298 log_debug("Whitelist[%zu] Ed25519 key: %s", i, whitelist_ed25519_hex);
299
300 // Direct comparison of Ed25519 keys (constant-time to prevent timing
301 // attacks)
302 if (sodium_memcmp(client_ed25519, ctx->client_whitelist[i].key, ED25519_PUBLIC_KEY_SIZE) == 0) {
303 key_found = true;
304 ctx->client_ed25519_key_verified = true;
305
306 // Store the client's Ed25519 key for signature verification
307 memcpy(&ctx->client_ed25519_key, &ctx->client_whitelist[i], sizeof(public_key_t));
308
309 log_debug("Client Ed25519 key authorized (whitelist entry %zu)", i);
310 if (strlen(ctx->client_whitelist[i].comment) > 0) {
311 log_info("Client identity: %s", ctx->client_whitelist[i].comment);
312 }
313 break;
314 }
315 }
316
317 if (!key_found) {
318 SET_ERRNO(ERROR_CRYPTO_AUTH, "Client Ed25519 key not in whitelist - rejecting connection");
319 // Don't send AUTH_FAILED here - wait until server_complete
320 // Just mark that the key was rejected
321 ctx->client_ed25519_key_verified = false;
322 }
323 } else if (client_sent_identity) {
324 // No whitelist checking - just store the client's Ed25519 key for later
325 // (Already stored at line 313-314, but keep this for clarity)
326 ctx->client_ed25519_key_verified = false;
327 }
328
329 // Set peer's X25519 encryption key - this also derives the shared secret
330 crypto_result_t crypto_result = crypto_set_peer_public_key(&ctx->crypto_ctx, client_x25519);
331 if (crypto_result != CRYPTO_OK) {
332 return SET_ERRNO(ERROR_CRYPTO, "Failed to set peer public key and derive shared secret: %s",
333 crypto_result_to_string(crypto_result));
334 }
335
336 // Clean up allocated memory
337 SAFE_FREE(client_ephemeral_key);
338 SAFE_FREE(client_identity_key);
339 SAFE_FREE(client_signature);
340
341 // Do authentication challenge if client provided identity key OR server
342 // requires password
343 if (client_sent_identity || ctx->crypto_ctx.has_password || ctx->require_client_auth) {
344 // Generate nonce and store it in the context
345 crypto_result = crypto_generate_nonce(ctx->crypto_ctx.auth_nonce);
346 if (crypto_result != CRYPTO_OK) {
347 return SET_ERRNO(ERROR_CRYPTO, "Failed to generate nonce: %s", crypto_result_to_string(crypto_result));
348 }
349
350 // Prepare AUTH_CHALLENGE packet: 1 byte flags + auth_challenge_size byte nonce
351 size_t challenge_packet_size = AUTH_CHALLENGE_FLAGS_SIZE + ctx->crypto_ctx.auth_challenge_size;
352 uint8_t challenge_packet[1 + HMAC_SHA256_SIZE]; // Maximum size buffer
353 uint8_t auth_flags = 0;
354
355 // Set flags based on server requirements
356 if (ctx->crypto_ctx.has_password) {
357 auth_flags |= AUTH_REQUIRE_PASSWORD;
358 }
359 if (ctx->require_client_auth) {
360 auth_flags |= AUTH_REQUIRE_CLIENT_KEY;
361 }
362
363 challenge_packet[0] = auth_flags;
364 memcpy(challenge_packet + 1, ctx->crypto_ctx.auth_nonce, ctx->crypto_ctx.auth_challenge_size);
365
366 // Send AUTH_CHALLENGE with flags + nonce (challenge_packet_size bytes)
367 result = packet_send_via_transport(transport, PACKET_TYPE_CRYPTO_AUTH_CHALLENGE, challenge_packet,
368 challenge_packet_size, 0);
369 if (result != 0) {
370 return SET_ERRNO(ERROR_NETWORK, "Failed to send AUTH_CHALLENGE packet");
371 }
372
373 ctx->state = CRYPTO_HANDSHAKE_AUTHENTICATING;
374 } else {
375 // No authentication needed - skip to completion
376 log_debug("Skipping authentication (no password and client has no identity key)");
377
378 // Send HANDSHAKE_COMPLETE immediately
379 result = packet_send_via_transport(transport, PACKET_TYPE_CRYPTO_HANDSHAKE_COMPLETE, NULL, 0, 0);
380 if (result != 0) {
381 return SET_ERRNO(ERROR_NETWORK, "Failed to send HANDSHAKE_COMPLETE packet");
382 }
383
384 ctx->state = CRYPTO_HANDSHAKE_READY;
385 ctx->crypto_ctx.handshake_complete = true; // Mark crypto context as ready for rekeying
386 log_debug("Crypto handshake completed successfully (no authentication)");
387 }
388
389 return ASCIICHAT_OK;
390}
391// Server: Process auth response and complete handshake
392asciichat_error_t crypto_handshake_server_complete(crypto_handshake_context_t *ctx, acip_transport_t *transport,
393 packet_type_t packet_type, const uint8_t *payload,
394 size_t payload_len) {
395 if (!ctx || ctx->state != CRYPTO_HANDSHAKE_AUTHENTICATING) {
396 return SET_ERRNO(ERROR_INVALID_STATE, "Invalid state: ctx=%p, state=%d", ctx, ctx ? ctx->state : -1);
397 }
398 if (!transport) {
399 return SET_ERRNO(ERROR_INVALID_PARAM, "transport is NULL");
400 }
401
402 // Note: Packet already received by ACIP handler
403 int result;
404
405 // Verify packet type
406 if (packet_type != PACKET_TYPE_CRYPTO_AUTH_RESPONSE) {
407 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Expected AUTH_RESPONSE, got packet type %d", packet_type);
408 }
409
410 // Verify password if required
411 if (ctx->crypto_ctx.has_password) {
412 // Validate packet size using session parameters
413 asciichat_error_t validation_result =
414 crypto_handshake_validate_packet_size(ctx, PACKET_TYPE_CRYPTO_AUTH_RESPONSE, payload_len);
415 if (validation_result != ASCIICHAT_OK) {
416 return validation_result;
417 }
418
419 // Ensure shared secret is derived before password verification
420 // This is critical for password HMAC verification which binds to the shared secret
421 if (!ctx->crypto_ctx.key_exchange_complete) {
422 SET_ERRNO(ERROR_CRYPTO, "Password authentication failed - key exchange not complete");
423
424 // Send AUTH_FAILED with specific reason
425 auth_failure_packet_t failure = {0};
426 failure.reason_flags = AUTH_FAIL_PASSWORD_INCORRECT;
427 if (ctx->require_client_auth) {
428 failure.reason_flags |= AUTH_FAIL_CLIENT_KEY_REQUIRED;
429 }
430 packet_send_via_transport(transport, PACKET_TYPE_CRYPTO_AUTH_FAILED, &failure, sizeof(failure), 0);
431 return ERROR_NETWORK;
432 }
433
434 // Verify password HMAC (binds to DH shared_secret to prevent MITM)
435 log_debug("Verifying password HMAC: has_password=%d, key_exchange_complete=%d", ctx->crypto_ctx.has_password,
436 ctx->crypto_ctx.key_exchange_complete);
437 if (!payload) {
438 SET_ERRNO(ERROR_INVALID_PARAM, "Payload is NULL in password authentication");
439 return ERROR_CRYPTO;
440 }
441 if (!crypto_verify_auth_response(&ctx->crypto_ctx, ctx->crypto_ctx.auth_nonce, payload)) {
442 log_debug("Password HMAC verification failed");
443 // Enhanced error message when both password and whitelist are required
444 if (ctx->require_client_auth) {
445 SET_ERRNO(ERROR_CRYPTO,
446 "Password authentication failed - incorrect password (server also requires whitelisted client key)");
447 } else {
448 SET_ERRNO(ERROR_CRYPTO, "Password authentication failed - incorrect password");
449 }
450
451 // Send AUTH_FAILED with specific reason
452 auth_failure_packet_t failure = {0};
453 failure.reason_flags = AUTH_FAIL_PASSWORD_INCORRECT;
454 if (ctx->require_client_auth) {
455 failure.reason_flags |= AUTH_FAIL_CLIENT_KEY_REQUIRED;
456 }
457 packet_send_via_transport(transport, PACKET_TYPE_CRYPTO_AUTH_FAILED, &failure, sizeof(failure), 0);
458 return ERROR_NETWORK;
459 }
460
461 // Extract client challenge nonce for mutual authentication
462 // Use ctx->crypto_ctx.hmac_size and ctx->crypto_ctx.auth_challenge_size (negotiated during handshake)
463 memcpy(ctx->client_challenge_nonce, payload + ctx->crypto_ctx.hmac_size, ctx->crypto_ctx.auth_challenge_size);
464 log_info("Password authentication successful");
465 } else {
466 // Ed25519 signature auth (payload format: signature + client_nonce + gpg_key_id_len + gpg_key_id)
467 size_t expected_min_signature_size =
468 ctx->crypto_ctx.signature_size + ctx->crypto_ctx.auth_challenge_size + 1; // +1 for gpg_key_id_len byte
469 size_t expected_password_size = ctx->crypto_ctx.hmac_size + ctx->crypto_ctx.auth_challenge_size;
470 if (!payload) {
471 SET_ERRNO(ERROR_INVALID_PARAM, "Payload is NULL in signature authentication");
472 return ERROR_CRYPTO;
473 }
474 if (payload_len >= expected_min_signature_size) {
475 // Signature + client nonce + optional GPG key ID - VERIFY the signature on the challenge nonce
476 const uint8_t *signature = payload;
477 const uint8_t *client_nonce = payload + ctx->crypto_ctx.signature_size;
478
479 // Extract GPG key ID if present
480 size_t gpg_offset = ctx->crypto_ctx.signature_size + ctx->crypto_ctx.auth_challenge_size;
481 uint8_t gpg_key_id_len = payload[gpg_offset];
482 const char *client_gpg_key_id = NULL;
483 char gpg_key_id_buffer[41] = {0};
484
485 if (gpg_key_id_len > 0 && gpg_key_id_len <= 40) {
486 // Validate packet has enough bytes for GPG key ID
487 if (payload_len >= gpg_offset + 1 + gpg_key_id_len) {
488 memcpy(gpg_key_id_buffer, payload + gpg_offset + 1, gpg_key_id_len);
489 gpg_key_id_buffer[gpg_key_id_len] = '\0';
490 client_gpg_key_id = gpg_key_id_buffer;
491 log_debug("Extracted client GPG key ID from AUTH_RESPONSE: %s", client_gpg_key_id);
492 }
493 }
494
495 // Actually verify the Ed25519/GPG signature on the challenge nonce
496 // This was missing, allowing authentication bypass
497 if (ctx->client_ed25519_key_verified) {
498 // Use unified verification function that handles both Ed25519 and GPG keys
499 // This matches the KEY_EXCHANGE verification path
500 log_debug("Verifying %s signature on challenge nonce",
501 ctx->client_ed25519_key.type == KEY_TYPE_GPG ? "GPG" : "Ed25519");
502
503 asciichat_error_t verify_result = ed25519_verify_signature(
504 ctx->client_ed25519_key.key, ctx->crypto_ctx.auth_nonce, ctx->crypto_ctx.auth_challenge_size, signature,
505 client_gpg_key_id // GPG key ID for fallback verification
506 );
507
508 if (verify_result != ASCIICHAT_OK) {
509 auth_failure_packet_t failure = {0};
510 failure.reason_flags = AUTH_FAIL_CLIENT_KEY_REJECTED;
511 SET_ERRNO(ERROR_CRYPTO_AUTH, "%s signature verification failed on challenge nonce",
512 ctx->client_ed25519_key.type == KEY_TYPE_GPG ? "GPG" : "Ed25519");
513 packet_send_via_transport(transport, PACKET_TYPE_CRYPTO_AUTH_FAILED, &failure, sizeof(failure), 0);
514 return ERROR_CRYPTO_AUTH;
515 }
516 log_debug("%s signature on challenge nonce verified successfully",
517 ctx->client_ed25519_key.type == KEY_TYPE_GPG ? "GPG" : "Ed25519");
518 }
519
520 memcpy(ctx->client_challenge_nonce, client_nonce, ctx->crypto_ctx.auth_challenge_size);
521 } else if (payload_len == expected_password_size) {
522 // Just client nonce (legacy or password-only mode without password enabled)
523 // Use ctx->crypto_ctx.hmac_size and ctx->crypto_ctx.auth_challenge_size (negotiated during handshake)
524 memcpy(ctx->client_challenge_nonce, payload + ctx->crypto_ctx.hmac_size, ctx->crypto_ctx.auth_challenge_size);
525 } else {
526 // Validate packet size using session parameters
527 asciichat_error_t validation_result =
528 crypto_handshake_validate_packet_size(ctx, PACKET_TYPE_CRYPTO_AUTH_RESPONSE, payload_len);
529 if (validation_result != ASCIICHAT_OK) {
530 return validation_result;
531 }
532 }
533 }
534
535 // Verify client key if required (whitelist)
536 if (ctx->require_client_auth) {
537 if (!ctx->client_ed25519_key_verified) {
538 // Send AUTH_FAILED with specific reason
539 auth_failure_packet_t failure = {0};
540
541 // Check if client provided a key but it wasn't in whitelist
542 if (ctx->client_sent_identity) {
543 SET_ERRNO(ERROR_CRYPTO_AUTH, "Client key authentication failed - your key is not in the server's whitelist");
544 failure.reason_flags = AUTH_FAIL_CLIENT_KEY_REJECTED;
545 } else {
546 SET_ERRNO(ERROR_CRYPTO_AUTH, "Client key authentication failed - client did not provide a key");
547 failure.reason_flags = AUTH_FAIL_CLIENT_KEY_REQUIRED;
548 }
549
550 if (ctx->crypto_ctx.has_password) {
551 // Password was verified, but key was not
552 SET_ERRNO(ERROR_CRYPTO_AUTH, "Note: Password was correct, but client key is required");
553 }
554 packet_send_via_transport(transport, PACKET_TYPE_CRYPTO_AUTH_FAILED, &failure, sizeof(failure), 0);
555 return ERROR_NETWORK;
556 }
557 log_info("Client key authentication successful (whitelist verified)");
558 if (strlen(ctx->client_ed25519_key.comment) > 0) {
559 log_info("Authenticated client: %s", ctx->client_ed25519_key.comment);
560 }
561 }
562
563 // Send SERVER_AUTH_RESPONSE with server's HMAC for mutual authentication
564 // Bind to DH shared_secret to prevent MITM (even if attacker knows password)
565 // Allocate buffer using negotiated hmac_size (should match AUTH_HMAC_SIZE)
566 uint8_t server_hmac[HMAC_SHA256_SIZE]; // Maximum size, actual size is ctx->hmac_size
567 crypto_result_t crypto_result =
568 crypto_compute_auth_response(&ctx->crypto_ctx, ctx->client_challenge_nonce, server_hmac);
569 if (crypto_result != CRYPTO_OK) {
570 SET_ERRNO(ERROR_CRYPTO, "Failed to compute server HMAC for mutual authentication: %s",
571 crypto_result_to_string(crypto_result));
572 return ERROR_NETWORK;
573 }
574
575 log_debug("Sending SERVER_AUTH_RESPONSE packet with server HMAC (%u bytes) "
576 "for mutual authentication",
577 ctx->crypto_ctx.hmac_size);
578 result = packet_send_via_transport(transport, PACKET_TYPE_CRYPTO_SERVER_AUTH_RESP, server_hmac,
579 ctx->crypto_ctx.hmac_size, 0);
580 if (result != ASCIICHAT_OK) {
581 SET_ERRNO(ERROR_NETWORK, "Failed to send SERVER_AUTH_RESPONSE packet");
582 return ERROR_NETWORK;
583 }
584
585 ctx->state = CRYPTO_HANDSHAKE_READY;
586 log_debug("Crypto handshake completed successfully (mutual authentication)");
587
588 return ASCIICHAT_OK;
589}
590
591// =============================================================================
592// Legacy TCP Socket Wrappers (backward compatibility)
593// =============================================================================
594// These wrappers maintain the old socket-based interface for TCP clients
595// that do handshake BEFORE creating ACIP transport. Will be removed in Phase 5.
596
600asciichat_error_t crypto_handshake_server_start_socket(crypto_handshake_context_t *ctx, socket_t client_socket) {
601 // Create temporary TCP transport for handshake
602 acip_transport_t *temp_transport = acip_tcp_transport_create(client_socket, NULL);
603 if (!temp_transport) {
604 return SET_ERRNO(ERROR_NETWORK, "Failed to create temporary transport");
605 }
606
607 asciichat_error_t result = crypto_handshake_server_start(ctx, temp_transport);
608
609 // Destroy temporary transport (socket remains open)
610 acip_transport_destroy(temp_transport);
611
612 return result;
613}
614
618asciichat_error_t crypto_handshake_server_auth_challenge_socket(crypto_handshake_context_t *ctx,
619 socket_t client_socket) {
620 // Receive packet using old method
621 packet_type_t packet_type;
622 uint8_t *payload = NULL;
623 size_t payload_len = 0;
624 int result = receive_packet(client_socket, &packet_type, (void **)&payload, &payload_len);
625 if (result != ASCIICHAT_OK) {
626 return SET_ERRNO(ERROR_NETWORK, "Failed to receive KEY_EXCHANGE_RESPONSE packet");
627 }
628
629 // Create temporary TCP transport
630 acip_transport_t *temp_transport = acip_tcp_transport_create(client_socket, NULL);
631 if (!temp_transport) {
632 // Payload will be freed below since we own it from receive_packet()
633 if (payload) {
634 buffer_pool_free(NULL, payload, payload_len);
635 }
636 return SET_ERRNO(ERROR_NETWORK, "Failed to create temporary transport");
637 }
638
639 // Call new function
640 asciichat_error_t handshake_result =
641 crypto_handshake_server_auth_challenge(ctx, temp_transport, packet_type, payload, payload_len);
642
643 // Free payload (handshake function makes copies of what it needs)
644 // We own the payload from receive_packet(), so we must free it
645 if (payload) {
646 buffer_pool_free(NULL, payload, payload_len);
647 }
648
649 // Destroy temporary transport
650 acip_transport_destroy(temp_transport);
651
652 return handshake_result;
653}
654
658asciichat_error_t crypto_handshake_server_complete_socket(crypto_handshake_context_t *ctx, socket_t client_socket) {
659 // Receive packet using old method
660 packet_type_t packet_type = 0;
661 uint8_t *payload = NULL;
662 size_t payload_len = 0;
663 int result = receive_packet(client_socket, &packet_type, (void **)&payload, &payload_len);
664 if (result != ASCIICHAT_OK) {
665 return SET_ERRNO(ERROR_NETWORK, "Failed to receive AUTH_RESPONSE packet");
666 }
667
668 // Create temporary TCP transport
669 acip_transport_t *temp_transport = acip_tcp_transport_create(client_socket, NULL);
670 if (!temp_transport) {
671 // Payload will be freed below since we own it from receive_packet()
672 if (payload) {
673 buffer_pool_free(NULL, payload, payload_len);
674 }
675 return SET_ERRNO(ERROR_NETWORK, "Failed to create temporary transport");
676 }
677
678 // Call new function
679 asciichat_error_t handshake_result =
680 crypto_handshake_server_complete(ctx, temp_transport, packet_type, payload, payload_len);
681
682 // Free payload (handshake function makes copies of what it needs)
683 // We own the payload from receive_packet(), so we must free it
684 if (payload) {
685 buffer_pool_free(NULL, payload, payload_len);
686 }
687
688 // Destroy temporary transport
689 acip_transport_destroy(temp_transport);
690
691 return handshake_result;
692}
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
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)
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_server_auth_challenge_socket(crypto_handshake_context_t *ctx, socket_t client_socket)
Legacy wrapper: Auth challenge using socket (TCP clients only)
asciichat_error_t crypto_handshake_server_start_socket(crypto_handshake_context_t *ctx, socket_t client_socket)
Legacy wrapper: Start handshake using socket (TCP clients only)
asciichat_error_t crypto_handshake_server_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_server_auth_challenge(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_server_start(crypto_handshake_context_t *ctx, acip_transport_t *transport)
asciichat_error_t crypto_handshake_server_complete_socket(crypto_handshake_context_t *ctx, socket_t client_socket)
Legacy wrapper: Complete handshake using socket (TCP clients only)
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)