ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
crypto/handshake/common.c
Go to the documentation of this file.
1
7#include <ascii-chat/common.h>
8#include <ascii-chat/asciichat_errno.h>
9#include <ascii-chat/buffer_pool.h>
10#include <ascii-chat/util/endian.h>
11#include <ascii-chat/crypto/crypto.h>
12#include <ascii-chat/crypto/handshake/common.h>
13#include <stdio.h>
14#include <string.h>
15
16// Initialize crypto handshake context
17asciichat_error_t crypto_handshake_init(crypto_handshake_context_t *ctx, bool is_server) {
18 if (!ctx) {
19 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: ctx=%p", ctx);
20 }
21
22 // Zero out the context
23 memset(ctx, 0, sizeof(crypto_handshake_context_t));
24
25 // Initialize core crypto context
26 crypto_result_t result = crypto_init(&ctx->crypto_ctx);
27 if (result != CRYPTO_OK) {
28 return SET_ERRNO(ERROR_CRYPTO, "Failed to initialize crypto context: %s", crypto_result_to_string(result));
29 }
30
31 ctx->state = CRYPTO_HANDSHAKE_INIT;
32 ctx->is_server = is_server;
33 ctx->verify_server_key = false;
34 ctx->require_client_auth = false;
35 ctx->server_uses_client_auth = false; // Set to true only if authenticated packet received
36
37 // Load server keys if this is a server
38 if (is_server) {
39 log_dev("Server crypto handshake initialized (ephemeral keys)");
40 } else {
41 log_dev("Client crypto handshake initialized");
42 }
43
44 return ASCIICHAT_OK;
45}
46
47// Set crypto parameters from crypto_parameters_packet_t
48asciichat_error_t crypto_handshake_set_parameters(crypto_handshake_context_t *ctx,
49 const crypto_parameters_packet_t *params) {
50 if (!ctx || !params) {
51 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: ctx=%p, params=%p", ctx, params);
52 }
53
54 // Client receives network byte order and must convert
55 // Server uses host byte order and must NOT convert
56 if (ctx->is_server) {
57 // Server: values are already in host byte order
58 // Update crypto context with negotiated parameters directly
59 ctx->crypto_ctx.public_key_size = params->kex_public_key_size;
60 ctx->crypto_ctx.auth_public_key_size = params->auth_public_key_size;
61 ctx->crypto_ctx.shared_key_size = params->shared_secret_size;
62 ctx->crypto_ctx.signature_size = params->signature_size;
63 } else {
64 // Client: convert from network byte order
65 // Update crypto context with negotiated parameters directly
66 ctx->crypto_ctx.public_key_size = NET_TO_HOST_U16(params->kex_public_key_size);
67 ctx->crypto_ctx.auth_public_key_size = NET_TO_HOST_U16(params->auth_public_key_size);
68 ctx->crypto_ctx.shared_key_size = NET_TO_HOST_U16(params->shared_secret_size);
69 ctx->crypto_ctx.signature_size = NET_TO_HOST_U16(params->signature_size);
70 }
71 // Update crypto context with negotiated parameters directly
72 ctx->crypto_ctx.nonce_size = params->nonce_size;
73 ctx->crypto_ctx.mac_size = params->mac_size;
74 ctx->crypto_ctx.hmac_size = params->hmac_size;
75 ctx->crypto_ctx.auth_challenge_size =
76 AUTH_CHALLENGE_SIZE; // Auth challenge size is fixed for now, could be negotiated later
77 ctx->crypto_ctx.encryption_key_size =
78 (uint8_t)ctx->crypto_ctx.shared_key_size; // Use shared key size as encryption key size
79 ctx->crypto_ctx.private_key_size = ctx->crypto_ctx.public_key_size; // Same as public key for X25519
80 ctx->crypto_ctx.salt_size = ARGON2ID_SALT_SIZE; // Salt size doesn't change
81
82 log_debug("Crypto parameters set: kex_key=%u, auth_key=%u, sig=%u, "
83 "secret=%u, nonce=%u, mac=%u, hmac=%u",
84 ctx->crypto_ctx.public_key_size, ctx->crypto_ctx.auth_public_key_size, ctx->crypto_ctx.signature_size,
85 ctx->crypto_ctx.shared_key_size, ctx->crypto_ctx.nonce_size, ctx->crypto_ctx.mac_size,
86 ctx->crypto_ctx.hmac_size);
87
88 return ASCIICHAT_OK;
89}
90
91// Validate crypto packet size based on session parameters
92asciichat_error_t crypto_handshake_validate_packet_size(const crypto_handshake_context_t *ctx, uint16_t packet_type,
93 size_t packet_size) {
94 if (!ctx) {
95 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: ctx=%p", ctx);
96 }
97
98 switch (packet_type) {
99 case PACKET_TYPE_CRYPTO_CAPABILITIES:
100 if (packet_size != sizeof(crypto_capabilities_packet_t)) {
101 // Don't return an error code, just set the errno and return the error code
102 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Invalid crypto capabilities packet size: %zu (expected %zu)",
103 packet_size, sizeof(crypto_capabilities_packet_t));
104 }
105 break;
106
107 case PACKET_TYPE_CRYPTO_PARAMETERS:
108 if (packet_size != sizeof(crypto_parameters_packet_t)) {
109 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Invalid crypto parameters packet size: %zu (expected %zu)", packet_size,
110 sizeof(crypto_parameters_packet_t));
111 }
112 break;
113
114 case PACKET_TYPE_CRYPTO_KEY_EXCHANGE_INIT:
115 // Server can send either:
116 // 1. Simple format: kex_public_key_size (when server has no identity key)
117 // 2. Authenticated format: kex_public_key_size + auth_public_key_size + signature_size
118 {
119 size_t simple_size = ctx->crypto_ctx.public_key_size;
120 size_t authenticated_size =
121 ctx->crypto_ctx.public_key_size + ctx->crypto_ctx.auth_public_key_size + ctx->crypto_ctx.signature_size;
122
123 if (packet_size != simple_size && packet_size != authenticated_size) {
124 return SET_ERRNO(ERROR_NETWORK_PROTOCOL,
125 "Invalid KEY_EXCHANGE_INIT size: %zu (expected %zu for simple or %zu for authenticated: "
126 "kex=%u + auth=%u + sig=%u)",
127 packet_size, simple_size, authenticated_size, ctx->crypto_ctx.public_key_size,
128 ctx->crypto_ctx.auth_public_key_size, ctx->crypto_ctx.signature_size);
129 }
130 }
131 break;
132
133 case PACKET_TYPE_CRYPTO_KEY_EXCHANGE_RESP:
134 // Client can send either:
135 // 1. Simple format: kex_public_key_size (when server has no identity key)
136 // 2. Authenticated format: kex_public_key_size + client_auth_key_size + client_sig_size + [gpg_key_id_len:1] +
137 // [gpg_key_id:0-16]
138 {
139 size_t simple_size = ctx->crypto_ctx.public_key_size;
140 // For authenticated format, use Ed25519 sizes since client has Ed25519 key
141 size_t ed25519_auth_size = ED25519_PUBLIC_KEY_SIZE; // Ed25519 public key is always 32 bytes
142 size_t ed25519_sig_size = ED25519_SIGNATURE_SIZE; // Ed25519 signature is always 64 bytes
143 size_t authenticated_min_size = ctx->crypto_ctx.public_key_size + ed25519_auth_size + ed25519_sig_size;
144 size_t authenticated_max_size = authenticated_min_size + 1 + 40; // +1 for length, +40 for max GPG key ID
145
146 if (packet_size != simple_size &&
147 (packet_size < authenticated_min_size || packet_size > authenticated_max_size)) {
148 return SET_ERRNO(ERROR_NETWORK_PROTOCOL,
149 "Invalid KEY_EXCHANGE_RESP size: %zu (expected %zu for simple or %zu-%zu for authenticated: "
150 "kex=%u + auth=%u + sig=%u + optional GPG key ID)",
151 packet_size, simple_size, authenticated_min_size, authenticated_max_size,
152 ctx->crypto_ctx.public_key_size, ed25519_auth_size, ed25519_sig_size);
153 }
154 }
155 break;
156
157 case PACKET_TYPE_CRYPTO_AUTH_CHALLENGE:
158 // Server sends: 1 byte auth_flags + auth_challenge_size byte nonce
159 {
160 size_t expected_size = AUTH_CHALLENGE_FLAGS_SIZE + ctx->crypto_ctx.auth_challenge_size;
161 if (packet_size != expected_size) {
162 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Invalid AUTH_CHALLENGE size: %zu (expected %zu: flags=%d + nonce=%u)",
163 packet_size, expected_size, AUTH_CHALLENGE_FLAGS_SIZE, ctx->crypto_ctx.auth_challenge_size);
164 }
165 }
166 break;
167
168 case PACKET_TYPE_CRYPTO_AUTH_RESPONSE:
169 // Client sends: hmac_size + auth_challenge_size bytes client_nonce + [gpg_key_id_len:1] + [gpg_key_id:0-40]
170 {
171 size_t min_size = ctx->crypto_ctx.hmac_size + ctx->crypto_ctx.auth_challenge_size;
172 size_t max_size = min_size + 1 + 40; // +1 for length, +40 for max GPG key ID
173 if (packet_size < min_size || packet_size > max_size) {
174 return SET_ERRNO(ERROR_NETWORK_PROTOCOL,
175 "Invalid AUTH_RESPONSE size: %zu (expected %zu-%zu: hmac=%u + "
176 "nonce=%u + optional GPG key ID)",
177 packet_size, min_size, max_size, ctx->crypto_ctx.hmac_size,
178 ctx->crypto_ctx.auth_challenge_size);
179 }
180 }
181 break;
182
183 case PACKET_TYPE_CRYPTO_AUTH_FAILED:
184 // Variable size - just check reasonable limits
185 if (packet_size > MAX_AUTH_FAILED_PACKET_SIZE) {
186 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Invalid AUTH_FAILED size: %zu (max %d)", packet_size,
187 MAX_AUTH_FAILED_PACKET_SIZE);
188 }
189 break;
190
191 case PACKET_TYPE_CRYPTO_SERVER_AUTH_RESP:
192 // Server sends: hmac_size bytes
193 if (packet_size != ctx->crypto_ctx.hmac_size) {
194 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Invalid SERVER_AUTH_RESP size: %zu (expected %u)", packet_size,
195 ctx->crypto_ctx.hmac_size);
196 }
197 break;
198
199 case PACKET_TYPE_CRYPTO_HANDSHAKE_COMPLETE:
200 // Empty packet
201 if (packet_size != 0) {
202 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Invalid HANDSHAKE_COMPLETE size: %zu (expected 0)", packet_size);
203 }
204 break;
205
206 case PACKET_TYPE_CRYPTO_NO_ENCRYPTION:
207 // Empty packet
208 if (packet_size != 0) {
209 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Invalid NO_ENCRYPTION size: %zu (expected 0)", packet_size);
210 }
211 break;
212
213 case PACKET_TYPE_ENCRYPTED:
214 // Variable size - check reasonable limits
215 if (packet_size > MAX_ENCRYPTED_PACKET_SIZE) { // 64KB max for encrypted packets
216 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Invalid ENCRYPTED size: %zu (max %d)", packet_size,
217 MAX_ENCRYPTED_PACKET_SIZE);
218 }
219 break;
220
221 default:
222 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Unknown crypto packet type: %u", packet_type);
223 }
224
225 return ASCIICHAT_OK;
226}
227
228// Initialize crypto handshake context with password authentication
229asciichat_error_t crypto_handshake_init_with_password(crypto_handshake_context_t *ctx, bool is_server,
230 const char *password) {
231 if (!ctx || !password) {
232 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: ctx=%p, password=%p", ctx, password);
233 }
234
235 // Zero out the context
236 memset(ctx, 0, sizeof(crypto_handshake_context_t));
237
238 // Initialize core crypto context with password
239 crypto_result_t result = crypto_init_with_password(&ctx->crypto_ctx, password);
240 if (result != CRYPTO_OK) {
241 return SET_ERRNO(ERROR_CRYPTO, "Failed to initialize crypto context with password: %s",
243 }
244
245 ctx->state = CRYPTO_HANDSHAKE_INIT;
246 ctx->is_server = is_server;
247 ctx->verify_server_key = false;
248 ctx->require_client_auth = false;
249 ctx->server_uses_client_auth = false; // Set to true only if authenticated packet received
250 ctx->has_password = true;
251
252 // Store password temporarily (will be cleared after key derivation)
253 SAFE_STRNCPY(ctx->password, password, sizeof(ctx->password) - 1);
254
255 return ASCIICHAT_OK;
256}
257
258// Cleanup crypto handshake context
259void crypto_handshake_destroy(crypto_handshake_context_t *ctx) {
260 if (!ctx)
261 return;
262
263 // Cleanup core crypto context
264 crypto_destroy(&ctx->crypto_ctx);
265
266 // Zero out sensitive data
267 sodium_memzero(ctx, sizeof(crypto_handshake_context_t));
268}
269
270// Check if handshake is complete and encryption is ready
271bool crypto_handshake_is_ready(const crypto_handshake_context_t *ctx) {
272 if (!ctx)
273 return false;
274 return ctx->state == CRYPTO_HANDSHAKE_READY && crypto_is_ready(&ctx->crypto_ctx);
275}
276
277// Get the crypto context for encryption/decryption
278const crypto_context_t *crypto_handshake_get_context(const crypto_handshake_context_t *ctx) {
279 if (!ctx || !crypto_handshake_is_ready(ctx))
280 return NULL;
281 return &ctx->crypto_ctx;
282}
283
284// Encrypt a packet using the established crypto context
285asciichat_error_t crypto_handshake_encrypt_packet(const crypto_handshake_context_t *ctx, const uint8_t *plaintext,
286 size_t plaintext_len, uint8_t *ciphertext, size_t ciphertext_size,
287 size_t *ciphertext_len) {
288 if (!ctx || !crypto_handshake_is_ready(ctx)) {
289 SET_ERRNO(ERROR_INVALID_STATE, "Invalid state: ctx=%p, ready=%d", ctx, ctx ? crypto_handshake_is_ready(ctx) : 0);
290 return ERROR_INVALID_STATE;
291 }
292
293 crypto_result_t result = crypto_encrypt((crypto_context_t *)&ctx->crypto_ctx, plaintext, plaintext_len, ciphertext,
294 ciphertext_size, ciphertext_len);
295 if (result != CRYPTO_OK) {
296 return SET_ERRNO(ERROR_NETWORK, "Failed to encrypt packet: %s", crypto_result_to_string(result));
297 }
298
299 return ASCIICHAT_OK;
300}
301
302// Decrypt a packet using the established crypto context
303asciichat_error_t crypto_handshake_decrypt_packet(const crypto_handshake_context_t *ctx, const uint8_t *ciphertext,
304 size_t ciphertext_len, uint8_t *plaintext, size_t plaintext_size,
305 size_t *plaintext_len) {
306 if (!ctx || !crypto_handshake_is_ready(ctx)) {
307 SET_ERRNO(ERROR_INVALID_STATE, "Invalid state: ctx=%p, ready=%d", ctx, ctx ? crypto_handshake_is_ready(ctx) : 0);
308 return ERROR_INVALID_STATE;
309 }
310
311 crypto_result_t result = crypto_decrypt((crypto_context_t *)&ctx->crypto_ctx, ciphertext, ciphertext_len, plaintext,
312 plaintext_size, plaintext_len);
313 if (result != CRYPTO_OK) {
314 return SET_ERRNO(ERROR_NETWORK, "Failed to decrypt packet: %s", crypto_result_to_string(result));
315 }
316
317 return ASCIICHAT_OK;
318}
319
320// Helper: Encrypt with automatic passthrough if crypto not ready
321asciichat_error_t crypto_encrypt_packet_or_passthrough(const crypto_handshake_context_t *ctx, bool crypto_ready,
322 const uint8_t *plaintext, size_t plaintext_len,
323 uint8_t *ciphertext, size_t ciphertext_size,
324 size_t *ciphertext_len) {
325 if (!crypto_ready) {
326 // No encryption - just copy data
327 if (plaintext_len > ciphertext_size) {
328 SET_ERRNO(ERROR_BUFFER, "Plaintext too large for ciphertext buffer: %zu > %zu", plaintext_len, ciphertext_size);
329 return ERROR_BUFFER;
330 }
331 memcpy(ciphertext, plaintext, plaintext_len);
332 *ciphertext_len = plaintext_len;
333 return ASCIICHAT_OK;
334 }
335
336 return crypto_handshake_encrypt_packet(ctx, plaintext, plaintext_len, ciphertext, ciphertext_size, ciphertext_len);
337}
338
339// Helper: Decrypt with automatic passthrough if crypto not ready
340asciichat_error_t crypto_decrypt_packet_or_passthrough(const crypto_handshake_context_t *ctx, bool crypto_ready,
341 const uint8_t *ciphertext, size_t ciphertext_len,
342 uint8_t *plaintext, size_t plaintext_size,
343 size_t *plaintext_len) {
344 if (!crypto_ready) {
345 // No encryption - just copy data
346 if (ciphertext_len > plaintext_size) {
347 SET_ERRNO(ERROR_BUFFER, "Ciphertext too large for plaintext buffer: %zu > %zu", ciphertext_len, plaintext_size);
348 return ERROR_BUFFER;
349 }
350 memcpy(plaintext, ciphertext, ciphertext_len);
351 *plaintext_len = ciphertext_len;
352 return ASCIICHAT_OK;
353 }
354
355 return crypto_handshake_decrypt_packet(ctx, ciphertext, ciphertext_len, plaintext, plaintext_size, plaintext_len);
356}
357
358// =============================================================================
359// Session Rekeying Protocol Implementation
360// =============================================================================
361
366asciichat_error_t crypto_handshake_rekey_request(crypto_handshake_context_t *ctx, socket_t socket) {
367 if (!ctx || !crypto_handshake_is_ready(ctx)) {
368 return SET_ERRNO(ERROR_INVALID_STATE, "Handshake not ready for rekeying: ctx=%p, ready=%d", ctx,
369 ctx ? crypto_handshake_is_ready(ctx) : 0);
370 }
371
372 // Initialize rekey process (generates new ephemeral keypair)
373 crypto_result_t result = crypto_rekey_init(&ctx->crypto_ctx);
374 if (result != CRYPTO_OK) {
375 return SET_ERRNO(ERROR_CRYPTO, "Failed to initialize rekey: %s", crypto_result_to_string(result));
376 }
377
378 // Send REKEY_REQUEST with new ephemeral public key (32 bytes)
379 log_debug("Sending REKEY_REQUEST with new ephemeral X25519 public key (32 bytes)");
380 int send_result =
381 send_packet(socket, PACKET_TYPE_CRYPTO_REKEY_REQUEST, ctx->crypto_ctx.temp_public_key, CRYPTO_PUBLIC_KEY_SIZE);
382 if (send_result != 0) {
383 crypto_rekey_abort(&ctx->crypto_ctx); // Clean up temp keys on failure
384 return SET_ERRNO(ERROR_NETWORK, "Failed to send REKEY_REQUEST packet");
385 }
386
387 log_debug("REKEY_REQUEST sent successfully, awaiting REKEY_RESPONSE");
388 return ASCIICHAT_OK;
389}
390
395asciichat_error_t crypto_handshake_rekey_response(crypto_handshake_context_t *ctx, socket_t socket) {
396 if (!ctx || !crypto_handshake_is_ready(ctx)) {
397 return SET_ERRNO(ERROR_INVALID_STATE, "Handshake not ready for rekeying: ctx=%p, ready=%d", ctx,
398 ctx ? crypto_handshake_is_ready(ctx) : 0);
399 }
400
401 if (!ctx->crypto_ctx.rekey_in_progress || !ctx->crypto_ctx.has_temp_key) {
402 return SET_ERRNO(ERROR_INVALID_STATE, "No rekey in progress or temp key missing");
403 }
404
405 // Send REKEY_RESPONSE with new ephemeral public key (32 bytes)
406 log_debug("Sending REKEY_RESPONSE with new ephemeral X25519 public key (32 bytes)");
407 int send_result =
408 send_packet(socket, PACKET_TYPE_CRYPTO_REKEY_RESPONSE, ctx->crypto_ctx.temp_public_key, CRYPTO_PUBLIC_KEY_SIZE);
409 if (send_result != 0) {
410 crypto_rekey_abort(&ctx->crypto_ctx); // Clean up temp keys on failure
411 return SET_ERRNO(ERROR_NETWORK, "Failed to send REKEY_RESPONSE packet");
412 }
413
414 log_debug("REKEY_RESPONSE sent successfully, awaiting REKEY_COMPLETE");
415 return ASCIICHAT_OK;
416}
417
423asciichat_error_t crypto_handshake_rekey_complete(crypto_handshake_context_t *ctx, socket_t socket) {
424 if (!ctx || !crypto_handshake_is_ready(ctx)) {
425 return SET_ERRNO(ERROR_INVALID_STATE, "Handshake not ready for rekeying: ctx=%p, ready=%d", ctx,
426 ctx ? crypto_handshake_is_ready(ctx) : 0);
427 }
428
429 if (!ctx->crypto_ctx.rekey_in_progress || !ctx->crypto_ctx.has_temp_key) {
430 return SET_ERRNO(ERROR_INVALID_STATE, "No rekey in progress or temp key missing");
431 }
432
433 // Encrypt empty payload with NEW key to prove possession
434 uint8_t plaintext[1] = {0}; // Minimal payload
435 uint8_t ciphertext[256]; // Sufficient for nonce + MAC + minimal payload
436 size_t ciphertext_len = 0;
437
438 // Temporarily swap keys to encrypt with NEW key
439 uint8_t old_shared_key[CRYPTO_SHARED_KEY_SIZE];
440 memcpy(old_shared_key, ctx->crypto_ctx.shared_key, CRYPTO_SHARED_KEY_SIZE);
441 memcpy(ctx->crypto_ctx.shared_key, ctx->crypto_ctx.temp_shared_key, CRYPTO_SHARED_KEY_SIZE);
442
443 // Encrypt with NEW key
444 crypto_result_t result =
445 crypto_encrypt(&ctx->crypto_ctx, plaintext, sizeof(plaintext), ciphertext, sizeof(ciphertext), &ciphertext_len);
446
447 // Restore old key immediately (commit will happen after successful send)
448 memcpy(ctx->crypto_ctx.shared_key, old_shared_key, CRYPTO_SHARED_KEY_SIZE);
449 sodium_memzero(old_shared_key, sizeof(old_shared_key));
450
451 if (result != CRYPTO_OK) {
452 crypto_rekey_abort(&ctx->crypto_ctx);
453 return SET_ERRNO(ERROR_CRYPTO, "Failed to encrypt REKEY_COMPLETE: %s", crypto_result_to_string(result));
454 }
455
456 // Send encrypted REKEY_COMPLETE
457 log_debug("Sending REKEY_COMPLETE (encrypted with NEW key, %zu bytes)", ciphertext_len);
458 int send_result = send_packet(socket, PACKET_TYPE_CRYPTO_REKEY_COMPLETE, ciphertext, ciphertext_len);
459 if (send_result != 0) {
460 crypto_rekey_abort(&ctx->crypto_ctx);
461 return SET_ERRNO(ERROR_NETWORK, "Failed to send REKEY_COMPLETE packet");
462 }
463
464 // Commit to new key (atomic switch)
465 result = crypto_rekey_commit(&ctx->crypto_ctx);
466 if (result != CRYPTO_OK) {
467 return SET_ERRNO(ERROR_CRYPTO, "Failed to commit rekey: %s", crypto_result_to_string(result));
468 }
469
470 log_debug("Session rekeying completed successfully (initiator side)");
471 return ASCIICHAT_OK;
472}
473
478asciichat_error_t crypto_handshake_process_rekey_request(crypto_handshake_context_t *ctx, const uint8_t *packet,
479 size_t packet_len) {
480 if (!ctx || !crypto_handshake_is_ready(ctx)) {
481 return SET_ERRNO(ERROR_INVALID_STATE, "Handshake not ready for rekeying: ctx=%p, ready=%d", ctx,
482 ctx ? crypto_handshake_is_ready(ctx) : 0);
483 }
484
485 // DDoS PROTECTION: Rate limit rekey requests
486 time_t now = time(NULL);
487 if (ctx->crypto_ctx.rekey_last_request_time > 0) {
488 time_t elapsed = now - ctx->crypto_ctx.rekey_last_request_time;
489 time_t min_request_interval_seconds = (time_t)(REKEY_MIN_REQUEST_INTERVAL / NS_PER_SEC_INT);
490 if (elapsed < min_request_interval_seconds) {
491 return SET_ERRNO(ERROR_CRYPTO,
492 "SECURITY: Rekey request rejected - too frequent (%ld sec since last, minimum %ld sec required)",
493 (long)elapsed, (long)min_request_interval_seconds);
494 }
495 }
496
497 // Update last request time
498 ctx->crypto_ctx.rekey_last_request_time = now;
499
500 // Validate packet size (should be 32 bytes for X25519 public key)
501 if (packet_len != CRYPTO_PUBLIC_KEY_SIZE) {
502 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid REKEY_REQUEST packet size: %zu (expected %d)", packet_len,
503 CRYPTO_PUBLIC_KEY_SIZE);
504 }
505
506 log_debug("Received REKEY_REQUEST with peer's new ephemeral public key (32 bytes)");
507
508 // Initialize our rekey process (generates our new ephemeral keypair)
509 crypto_result_t result = crypto_rekey_init(&ctx->crypto_ctx);
510 if (result != CRYPTO_OK) {
511 return SET_ERRNO(ERROR_CRYPTO, "Failed to initialize rekey: %s", crypto_result_to_string(result));
512 }
513
514 // Process peer's public key and compute new shared secret
515 result = crypto_rekey_process_request(&ctx->crypto_ctx, packet);
516 if (result != CRYPTO_OK) {
517 crypto_rekey_abort(&ctx->crypto_ctx);
518 return SET_ERRNO(ERROR_CRYPTO, "Failed to process REKEY_REQUEST: %s", crypto_result_to_string(result));
519 }
520
521 log_debug("REKEY_REQUEST processed successfully, new shared secret computed (responder side)");
522 return ASCIICHAT_OK;
523}
524
529asciichat_error_t crypto_handshake_process_rekey_response(crypto_handshake_context_t *ctx, const uint8_t *packet,
530 size_t packet_len) {
531 if (!ctx || !crypto_handshake_is_ready(ctx)) {
532 return SET_ERRNO(ERROR_INVALID_STATE, "Handshake not ready for rekeying: ctx=%p, ready=%d", ctx,
533 ctx ? crypto_handshake_is_ready(ctx) : 0);
534 }
535
536 // Validate packet size (should be 32 bytes for X25519 public key)
537 if (packet_len != CRYPTO_PUBLIC_KEY_SIZE) {
538 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid REKEY_RESPONSE packet size: %zu (expected %d)", packet_len,
539 CRYPTO_PUBLIC_KEY_SIZE);
540 }
541
542 if (!ctx->crypto_ctx.rekey_in_progress || !ctx->crypto_ctx.has_temp_key) {
543 return SET_ERRNO(ERROR_INVALID_STATE, "No rekey in progress or temp key missing");
544 }
545
546 log_debug("Received REKEY_RESPONSE with peer's new ephemeral public key (32 bytes)");
547
548 // Process peer's public key and compute new shared secret
549 crypto_result_t result = crypto_rekey_process_response(&ctx->crypto_ctx, packet);
550 if (result != CRYPTO_OK) {
551 crypto_rekey_abort(&ctx->crypto_ctx);
552 return SET_ERRNO(ERROR_CRYPTO, "Failed to process REKEY_RESPONSE: %s", crypto_result_to_string(result));
553 }
554
555 log_debug("REKEY_RESPONSE processed successfully, new shared secret computed (initiator side)");
556 return ASCIICHAT_OK;
557}
558
564asciichat_error_t crypto_handshake_process_rekey_complete(crypto_handshake_context_t *ctx, const uint8_t *packet,
565 size_t packet_len) {
566 if (!ctx || !crypto_handshake_is_ready(ctx)) {
567 return SET_ERRNO(ERROR_INVALID_STATE, "Handshake not ready for rekeying: ctx=%p, ready=%d", ctx,
568 ctx ? crypto_handshake_is_ready(ctx) : 0);
569 }
570
571 if (!ctx->crypto_ctx.rekey_in_progress || !ctx->crypto_ctx.has_temp_key) {
572 return SET_ERRNO(ERROR_INVALID_STATE, "No rekey in progress or temp key missing");
573 }
574
575 log_debug("Received REKEY_COMPLETE packet (%zu bytes), verifying with NEW key", packet_len);
576
577 // Temporarily swap keys to decrypt with NEW key
578 uint8_t old_shared_key[CRYPTO_SHARED_KEY_SIZE];
579 memcpy(old_shared_key, ctx->crypto_ctx.shared_key, CRYPTO_SHARED_KEY_SIZE);
580 memcpy(ctx->crypto_ctx.shared_key, ctx->crypto_ctx.temp_shared_key, CRYPTO_SHARED_KEY_SIZE);
581
582 // Attempt to decrypt with NEW key
583 uint8_t plaintext[256];
584 size_t plaintext_len = 0;
585 crypto_result_t result =
586 crypto_decrypt(&ctx->crypto_ctx, packet, packet_len, plaintext, sizeof(plaintext), &plaintext_len);
587
588 // Restore old key immediately
589 memcpy(ctx->crypto_ctx.shared_key, old_shared_key, CRYPTO_SHARED_KEY_SIZE);
590 sodium_memzero(old_shared_key, sizeof(old_shared_key));
591
592 if (result != CRYPTO_OK) {
593 crypto_rekey_abort(&ctx->crypto_ctx);
594 return SET_ERRNO(ERROR_CRYPTO, "REKEY_COMPLETE decryption failed (key mismatch): %s",
596 }
597
598 log_debug("REKEY_COMPLETE verified successfully, committing to new key");
599
600 // Commit to new key (atomic switch)
601 result = crypto_rekey_commit(&ctx->crypto_ctx);
602 if (result != CRYPTO_OK) {
603 return SET_ERRNO(ERROR_CRYPTO, "Failed to commit rekey: %s", crypto_result_to_string(result));
604 }
605
606 log_debug("Session rekeying completed successfully (responder side)");
607 return ASCIICHAT_OK;
608}
609
614bool crypto_handshake_should_rekey(const crypto_handshake_context_t *ctx) {
615 if (!ctx || !crypto_handshake_is_ready(ctx)) {
616 return false;
617 }
618 return crypto_should_rekey(&ctx->crypto_ctx);
619}
asciichat_error_t crypto_handshake_rekey_complete(crypto_handshake_context_t *ctx, socket_t socket)
const crypto_context_t * crypto_handshake_get_context(const crypto_handshake_context_t *ctx)
void crypto_handshake_destroy(crypto_handshake_context_t *ctx)
asciichat_error_t crypto_handshake_decrypt_packet(const crypto_handshake_context_t *ctx, const uint8_t *ciphertext, size_t ciphertext_len, uint8_t *plaintext, size_t plaintext_size, size_t *plaintext_len)
asciichat_error_t crypto_handshake_process_rekey_request(crypto_handshake_context_t *ctx, const uint8_t *packet, size_t packet_len)
asciichat_error_t crypto_handshake_validate_packet_size(const crypto_handshake_context_t *ctx, uint16_t packet_type, size_t packet_size)
asciichat_error_t crypto_handshake_encrypt_packet(const crypto_handshake_context_t *ctx, const uint8_t *plaintext, size_t plaintext_len, uint8_t *ciphertext, size_t ciphertext_size, size_t *ciphertext_len)
asciichat_error_t crypto_handshake_init_with_password(crypto_handshake_context_t *ctx, bool is_server, const char *password)
bool crypto_handshake_is_ready(const crypto_handshake_context_t *ctx)
bool crypto_handshake_should_rekey(const crypto_handshake_context_t *ctx)
asciichat_error_t crypto_handshake_init(crypto_handshake_context_t *ctx, bool is_server)
asciichat_error_t crypto_handshake_rekey_response(crypto_handshake_context_t *ctx, socket_t socket)
asciichat_error_t crypto_handshake_set_parameters(crypto_handshake_context_t *ctx, const crypto_parameters_packet_t *params)
asciichat_error_t crypto_handshake_process_rekey_response(crypto_handshake_context_t *ctx, const uint8_t *packet, size_t packet_len)
asciichat_error_t crypto_encrypt_packet_or_passthrough(const crypto_handshake_context_t *ctx, bool crypto_ready, const uint8_t *plaintext, size_t plaintext_len, uint8_t *ciphertext, size_t ciphertext_size, size_t *ciphertext_len)
asciichat_error_t crypto_decrypt_packet_or_passthrough(const crypto_handshake_context_t *ctx, bool crypto_ready, const uint8_t *ciphertext, size_t ciphertext_len, uint8_t *plaintext, size_t plaintext_size, size_t *plaintext_len)
asciichat_error_t crypto_handshake_process_rekey_complete(crypto_handshake_context_t *ctx, const uint8_t *packet, size_t packet_len)
asciichat_error_t crypto_handshake_rekey_request(crypto_handshake_context_t *ctx, socket_t socket)
int socket_t
crypto_result_t crypto_rekey_commit(crypto_context_t *ctx)
crypto_result_t crypto_encrypt(crypto_context_t *ctx, const uint8_t *plaintext, size_t plaintext_len, uint8_t *ciphertext_out, size_t ciphertext_out_size, size_t *ciphertext_len_out)
crypto_result_t crypto_init(crypto_context_t *ctx)
const char * crypto_result_to_string(crypto_result_t result)
bool crypto_should_rekey(const crypto_context_t *ctx)
void crypto_rekey_abort(crypto_context_t *ctx)
bool crypto_is_ready(const crypto_context_t *ctx)
crypto_result_t crypto_rekey_process_request(crypto_context_t *ctx, const uint8_t *peer_new_public_key)
crypto_result_t crypto_decrypt(crypto_context_t *ctx, const uint8_t *ciphertext, size_t ciphertext_len, uint8_t *plaintext_out, size_t plaintext_out_size, size_t *plaintext_len_out)
crypto_result_t crypto_init_with_password(crypto_context_t *ctx, const char *password)
void crypto_destroy(crypto_context_t *ctx)
crypto_result_t crypto_rekey_init(crypto_context_t *ctx)
crypto_result_t crypto_rekey_process_response(crypto_context_t *ctx, const uint8_t *peer_new_public_key)
int send_packet(socket_t sockfd, packet_type_t type, const void *data, size_t len)
Send a basic packet without encryption.
Definition packet.c:753