ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
lib/crypto/crypto.c
Go to the documentation of this file.
1
7#include "crypto.h"
8#include "common.h"
9#include "asciichat_errno.h"
10#include "tests/test_env.h"
11#include "util/time.h"
12
13#include <string.h>
14#include <time.h>
15#include <inttypes.h>
16#include <stdatomic.h>
17
18// Static initialization flag for libsodium (atomic for thread-safe check)
19static _Atomic bool g_libsodium_initialized = false;
20
21// Internal packet type constants (for test helper functions only)
22// These are NOT part of the main network protocol - they're used for simple
23// packet serialization in crypto module test utilities
24static const uint32_t CRYPTO_PACKET_PUBLIC_KEY = 100;
25static const uint32_t CRYPTO_PACKET_ENCRYPTED_DATA = 102;
26static const uint32_t CRYPTO_PACKET_AUTH_CHALLENGE = 103;
27static const uint32_t CRYPTO_PACKET_AUTH_RESPONSE = 104;
28
29// =============================================================================
30// Internal helper functions
31// =============================================================================
32
33// Initialize libsodium (thread-safe, idempotent)
34static crypto_result_t init_libsodium(void) {
35 // Fast path: if already initialized, return immediately
36 if (atomic_load(&g_libsodium_initialized)) {
37 return CRYPTO_OK;
38 }
39
40 // Slow path: attempt initialization
41 // Note: sodium_init() is itself idempotent and thread-safe, but we use
42 // atomic flag to prevent redundant calls from multiple threads
43 if (sodium_init() < 0) {
44 SET_ERRNO(ERROR_CRYPTO, "Failed to initialize libsodium");
46 }
47
48 atomic_store(&g_libsodium_initialized, true);
49 return CRYPTO_OK;
50}
51
52// Generate secure nonce with session ID and counter to prevent reuse
53static void generate_nonce(crypto_context_t *ctx, uint8_t *nonce_out) {
54 // Nonce format (dynamic size based on algorithm):
55 // - Bytes 0-15: Session ID (constant per connection, random across connections)
56 // - Bytes 16+: Counter (increments per packet, prevents reuse within session)
57 //
58 // This prevents replay attacks both within and across sessions:
59 // - Different session_id prevents cross-session replay
60 // - Counter prevents within-session replay
61
62 SAFE_MEMCPY(nonce_out, 16, ctx->session_id, 16);
63 uint64_t counter = ctx->nonce_counter++;
64 size_t counter_size = ctx->nonce_size - 16;
65 SAFE_MEMCPY(nonce_out + 16, counter_size, &counter,
66 (counter_size < sizeof(counter)) ? counter_size : sizeof(counter));
67}
68
69// Securely clear memory
70static void secure_memzero(void *ptr, size_t len) {
71 sodium_memzero(ptr, len);
72}
73
74// =============================================================================
75// Core initialization and setup
76// =============================================================================
77
79 if (!ctx) {
80 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: ctx=%p", ctx);
82 }
83
84 // Initialize libsodium
85 crypto_result_t result = init_libsodium();
86 if (result != CRYPTO_OK) {
87 return result;
88 }
89
90 // Clear context
91 secure_memzero(ctx, sizeof(crypto_context_t));
92
93 // Generate key pair for X25519 key exchange
94 result = crypto_generate_keypair(ctx);
95 if (result != CRYPTO_OK) {
96 return result;
97 }
98
99 ctx->initialized = true;
100 ctx->has_password = false;
101 ctx->key_exchange_complete = false;
102 ctx->peer_key_received = false;
103 ctx->handshake_complete = false;
104 ctx->nonce_counter = 1; // Start from 1 (0 reserved for testing)
105 ctx->bytes_encrypted = 0;
106 ctx->bytes_decrypted = 0;
107
108 // Set default algorithm-specific parameters (can be overridden during handshake)
119
120 // Generate unique session ID to prevent replay attacks across connections
121 randombytes_buf(ctx->session_id, sizeof(ctx->session_id));
122
123 // Initialize rekeying state
124 ctx->rekey_packet_count = 0;
125 ctx->rekey_last_time = time(NULL); // Initialize to current time
126 ctx->rekey_last_request_time = 0; // No rekey request yet
127 ctx->rekey_in_progress = false;
128 ctx->rekey_failure_count = 0;
129 ctx->has_temp_key = false;
130 ctx->rekey_count = 0;
131
132 // SECURITY: Use production-safe rekey thresholds by default
133 // Rekey every 1 hour OR 1 million packets (whichever comes first)
134 // Only use test mode if explicitly requested via environment variable
135 if (is_test_environment()) {
138 char duration_str[32];
139 format_duration_s((double)ctx->rekey_time_threshold, duration_str, sizeof(duration_str));
140 log_info("Crypto context initialized with X25519 key exchange (TEST MODE rekey thresholds: %llu packets, %s)",
141 (unsigned long long)ctx->rekey_packet_threshold, duration_str);
142 } else {
143 ctx->rekey_packet_threshold = REKEY_DEFAULT_PACKET_THRESHOLD; // 1 million packets
144 ctx->rekey_time_threshold = REKEY_DEFAULT_TIME_THRESHOLD; // 3600 seconds (1 hour)
145 char duration_str[32];
146 format_duration_s((double)ctx->rekey_time_threshold, duration_str, sizeof(duration_str));
147 log_info("Crypto context initialized with X25519 key exchange (rekey thresholds: %llu packets, %s)",
148 (unsigned long long)ctx->rekey_packet_threshold, duration_str);
149 }
150 return CRYPTO_OK;
151}
152
154 if (!ctx || !password) {
155 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: ctx=%p, password=%p", ctx, password);
157 }
158
159 if (strlen(password) == 0) {
160 SET_ERRNO(ERROR_INVALID_PARAM, "Password cannot be empty");
162 }
163
164 // First initialize basic crypto
165 crypto_result_t result = crypto_init(ctx);
166 if (result != CRYPTO_OK) {
167 return result;
168 }
169
170 // Derive password key
171 result = crypto_derive_password_key(ctx, password);
172 if (result != CRYPTO_OK) {
173 crypto_cleanup(ctx);
174 return result;
175 }
176
177 ctx->has_password = true;
178
179 log_info("Crypto context initialized with password-based encryption");
180 return CRYPTO_OK;
181}
182
184 if (!ctx || !ctx->initialized) {
185 return;
186 }
187
188 // Securely wipe sensitive data
189 secure_memzero(ctx->private_key, sizeof(ctx->private_key));
190 secure_memzero(ctx->shared_key, sizeof(ctx->shared_key));
191 secure_memzero(ctx->password_key, sizeof(ctx->password_key));
192 secure_memzero(ctx->password_salt, sizeof(ctx->password_salt));
193
194 log_debug("Crypto context cleaned up (encrypted: %lu bytes, decrypted: %lu bytes)", ctx->bytes_encrypted,
195 ctx->bytes_decrypted);
196
197 // Clear entire context
198 secure_memzero(ctx, sizeof(crypto_context_t));
199}
200
202 if (!ctx) {
203 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_generate_keypair: NULL context");
205 }
206
207 // Generate X25519 key pair for key exchange
208 if (crypto_box_keypair(ctx->public_key, ctx->private_key) != 0) {
209 SET_ERRNO(ERROR_CRYPTO, "Failed to generate X25519 key pair");
211 }
212
213 log_debug("Generated X25519 key pair for key exchange");
214 return CRYPTO_OK;
215}
216
217// =============================================================================
218// Key exchange protocol (automatic HTTPS-like key exchange)
219// =============================================================================
220
222 if (!ctx || !ctx->initialized || !public_key_out) {
224 "crypto_get_public_key: Invalid parameters (ctx=%p, initialized=%d, public_key_out=%p)", ctx,
225 ctx ? ctx->initialized : 0, public_key_out);
227 }
228
229 // Bounds check to prevent buffer overflow
230 size_t copy_size = (ctx->public_key_size <= X25519_KEY_SIZE) ? ctx->public_key_size : X25519_KEY_SIZE;
231 SAFE_MEMCPY(public_key_out, copy_size, ctx->public_key, copy_size);
232 return CRYPTO_OK;
233}
234
236 if (!ctx || !ctx->initialized || !peer_public_key) {
238 "crypto_set_peer_public_key: Invalid parameters (ctx=%p, initialized=%d, peer_public_key=%p)", ctx,
239 ctx ? ctx->initialized : 0, peer_public_key);
241 }
242
243 // Store peer's public key
244 // Bounds check to prevent buffer overflow
245 size_t copy_size = (ctx->public_key_size <= X25519_KEY_SIZE) ? ctx->public_key_size : X25519_KEY_SIZE;
246 SAFE_MEMCPY(ctx->peer_public_key, copy_size, peer_public_key, copy_size);
247 ctx->peer_key_received = true;
248
249 // Compute shared secret using X25519
250 if (crypto_box_beforenm(ctx->shared_key, peer_public_key, ctx->private_key) != 0) {
251 SET_ERRNO(ERROR_CRYPTO, "Failed to compute shared secret from peer public key");
253 }
254
255 ctx->key_exchange_complete = true;
256
257 log_debug("Key exchange completed - shared secret computed");
258 return CRYPTO_OK;
259}
260
262 if (!ctx || !ctx->initialized) {
263 return false;
264 }
265
266 // Ready if either key exchange is complete OR password is set
267 return ctx->key_exchange_complete || ctx->has_password;
268}
269
270// =============================================================================
271// Password-based encryption (optional additional layer)
272// =============================================================================
273
275 if (!password) {
276 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_validate_password: Password is NULL");
278 }
279
280 size_t password_len = strlen(password);
281
282 if (password_len < MIN_PASSWORD_LENGTH) {
283 SET_ERRNO(ERROR_INVALID_PARAM, "Password too short (minimum %d characters, got %zu)", MIN_PASSWORD_LENGTH,
284 password_len);
286 }
287
288 if (password_len > MAX_PASSWORD_LENGTH) {
289 SET_ERRNO(ERROR_INVALID_PARAM, "Password too long (maximum %d characters, got %zu)", MAX_PASSWORD_LENGTH,
290 password_len);
292 }
293
294 return CRYPTO_OK;
295}
296
298 if (!ctx || !ctx->initialized || !password) {
300 "crypto_derive_password_key: Invalid parameters (ctx=%p, initialized=%d, password=%p)", ctx,
301 ctx ? ctx->initialized : 0, password);
303 }
304
305 // Validate password length requirements
306 crypto_result_t validation_result = crypto_validate_password(password);
307 if (validation_result != CRYPTO_OK) {
308 return validation_result;
309 }
310
311 // Use deterministic salt for consistent key derivation across client/server
312 // This ensures the same password produces the same key on both sides
313 // Salt must be exactly ARGON2ID_SALT_SIZE (32) bytes
314 const char *deterministic_salt = "ascii-chat-password-salt-v1";
315 size_t salt_str_len = strlen(deterministic_salt);
316
317 // Zero-initialize the salt buffer first
318 memset(ctx->password_salt, 0, ctx->salt_size);
319
320 // Copy the salt string (will be padded with zeros to ctx->salt_size)
321 memcpy(ctx->password_salt, deterministic_salt, (salt_str_len < ctx->salt_size) ? salt_str_len : ctx->salt_size);
322
323 // Derive key using Argon2id (memory-hard, secure against GPU attacks)
324 if (crypto_pwhash(ctx->password_key, ctx->encryption_key_size, password, strlen(password), ctx->password_salt,
325 crypto_pwhash_OPSLIMIT_INTERACTIVE, // ~0.1 seconds
326 crypto_pwhash_MEMLIMIT_INTERACTIVE, // ~64MB
327 crypto_pwhash_ALG_DEFAULT) != 0) {
328 SET_ERRNO(ERROR_CRYPTO, "Password key derivation failed - possibly out of memory");
330 }
331
332 log_debug("Password key derived successfully using Argon2id with deterministic salt");
333 return CRYPTO_OK;
334}
335
336bool crypto_verify_password(const crypto_context_t *ctx, const char *password) {
337 if (!ctx || !ctx->initialized || !ctx->has_password || !password) {
338 return false;
339 }
340
341 uint8_t test_key[SECRETBOX_KEY_SIZE]; // Use maximum size for buffer
342
343 // Use the same deterministic salt for verification
344 // Salt must be exactly ARGON2ID_SALT_SIZE (32) bytes
345 const char *deterministic_salt = "ascii-chat-password-salt-v1";
346 uint8_t salt[ARGON2ID_SALT_SIZE]; // Use maximum size for buffer
347 size_t salt_str_len = strlen(deterministic_salt);
348
349 // Zero-initialize the salt buffer first
350 memset(salt, 0, ARGON2ID_SALT_SIZE);
351
352 // Copy the salt string (will be padded with zeros to ctx->salt_size)
353 memcpy(salt, deterministic_salt, (salt_str_len < ctx->salt_size) ? salt_str_len : ctx->salt_size);
354
355 // Derive key with same salt
356 if (crypto_pwhash(test_key, ctx->encryption_key_size, password, strlen(password), salt,
357 crypto_pwhash_OPSLIMIT_INTERACTIVE, crypto_pwhash_MEMLIMIT_INTERACTIVE,
358 crypto_pwhash_ALG_DEFAULT) != 0) {
359 secure_memzero(test_key, sizeof(test_key));
360 return false;
361 }
362
363 // Constant-time comparison
364 bool match = (sodium_memcmp(test_key, ctx->password_key, ctx->encryption_key_size) == 0);
365
366 secure_memzero(test_key, sizeof(test_key));
367 return match;
368}
369
370// Derive a deterministic encryption key from password for handshake
372 uint8_t encryption_key[SECRETBOX_KEY_SIZE]) {
373 if (!password || !encryption_key) {
375 "crypto_derive_password_encryption_key: Invalid parameters (password=%p, encryption_key=%p)", password,
376 encryption_key);
378 }
379
380 // Validate password length requirements
381 crypto_result_t validation_result = crypto_validate_password(password);
382 if (validation_result != CRYPTO_OK) {
383 return validation_result;
384 }
385
386 // Use deterministic salt for consistent key derivation across client/server
387 // Salt must be exactly ARGON2ID_SALT_SIZE (32) bytes
388 const char *deterministic_salt = "ascii-chat-password-salt-v1";
389 uint8_t salt[ARGON2ID_SALT_SIZE]; // Use maximum size for buffer
390 size_t salt_str_len = strlen(deterministic_salt);
391
392 // Zero-initialize the salt buffer first
393 memset(salt, 0, ARGON2ID_SALT_SIZE);
394
395 // Copy the salt string (will be padded with zeros to ARGON2ID_SALT_SIZE)
396 memcpy(salt, deterministic_salt, (salt_str_len < ARGON2ID_SALT_SIZE) ? salt_str_len : ARGON2ID_SALT_SIZE);
397
398 // Derive key using Argon2id (memory-hard, secure against GPU attacks)
399 if (crypto_pwhash(encryption_key, SECRETBOX_KEY_SIZE, password, strlen(password), salt,
400 crypto_pwhash_OPSLIMIT_INTERACTIVE, // ~0.1 seconds
401 crypto_pwhash_MEMLIMIT_INTERACTIVE, // ~64MB
402 crypto_pwhash_ALG_DEFAULT) != 0) {
403 SET_ERRNO(ERROR_CRYPTO, "Password encryption key derivation failed - possibly out of memory");
405 }
406
407 log_debug("Password encryption key derived successfully using Argon2id");
408 return CRYPTO_OK;
409}
410
411// =============================================================================
412// Encryption/Decryption operations
413// =============================================================================
414
415crypto_result_t crypto_encrypt(crypto_context_t *ctx, const uint8_t *plaintext, size_t plaintext_len,
416 uint8_t *ciphertext_out, size_t ciphertext_out_size, size_t *ciphertext_len_out) {
417 if (!ctx || !ctx->initialized || !plaintext || !ciphertext_out || !ciphertext_len_out) {
419 "Invalid parameters: ctx=%p, initialized=%d, plaintext=%p, ciphertext_out=%p, ciphertext_len_out=%p", ctx,
420 ctx ? ctx->initialized : 0, plaintext, ciphertext_out, ciphertext_len_out);
422 }
423
424 if (plaintext_len == 0 || plaintext_len > CRYPTO_MAX_PLAINTEXT_SIZE) {
425 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid plaintext length: %zu (max: %d)", plaintext_len, CRYPTO_MAX_PLAINTEXT_SIZE);
427 }
428
429 if (!crypto_is_ready(ctx)) {
430 SET_ERRNO(ERROR_CRYPTO, "Crypto context not ready for encryption");
432 }
433
434 // Check output buffer size
435 size_t required_size = plaintext_len + ctx->nonce_size + ctx->mac_size;
436 if (ciphertext_out_size < required_size) {
437 SET_ERRNO(ERROR_BUFFER, "Ciphertext buffer too small: %zu < %zu", ciphertext_out_size, required_size);
439 }
440
441 // Check for nonce counter exhaustion (extremely unlikely in practice)
442 // Starting from 1, reaching UINT64_MAX would require ~292 billion years at 60 FPS.
443 // With key rotation required at 1M packets (~16 seconds), exhaustion is virtually impossible.
444 // This check is a safety fallback that should never trigger in practice.
445 if (ctx->nonce_counter == 0 || ctx->nonce_counter == UINT64_MAX) {
446 SET_ERRNO(ERROR_CRYPTO, "Nonce counter exhausted - key rotation required");
448 }
449
450 // Generate nonce and place at beginning of ciphertext
451 uint8_t nonce[XSALSA20_NONCE_SIZE]; // Use maximum nonce size for buffer
452 generate_nonce(ctx, nonce);
453 SAFE_MEMCPY(ciphertext_out, ctx->nonce_size, nonce, ctx->nonce_size);
454
455 // Choose encryption key (prefer shared key over password key)
456 const uint8_t *encryption_key = NULL;
457 if (ctx->key_exchange_complete) {
458 encryption_key = ctx->shared_key;
459 } else if (ctx->has_password) {
460 encryption_key = ctx->password_key;
461 } else {
462 SET_ERRNO(ERROR_CRYPTO, "No encryption key available");
464 }
465
466 // Encrypt using NaCl secretbox (XSalsa20 + Poly1305)
467 if (crypto_secretbox_easy(ciphertext_out + ctx->nonce_size, plaintext, plaintext_len, nonce, encryption_key) != 0) {
468 SET_ERRNO(ERROR_CRYPTO, "Encryption failed");
470 }
471
472 *ciphertext_len_out = required_size;
473 ctx->bytes_encrypted += plaintext_len;
474
475 // Increment rekey packet counter for rekeying trigger detection
476 ctx->rekey_packet_count++;
477
478 return CRYPTO_OK;
479}
480
481crypto_result_t crypto_decrypt(crypto_context_t *ctx, const uint8_t *ciphertext, size_t ciphertext_len,
482 uint8_t *plaintext_out, size_t plaintext_out_size, size_t *plaintext_len_out) {
483 if (!ctx || !ctx->initialized || !ciphertext || !plaintext_out || !plaintext_len_out) {
485 "Invalid parameters: ctx=%p, initialized=%d, ciphertext=%p, plaintext_out=%p, plaintext_len_out=%p", ctx,
486 ctx ? ctx->initialized : 0, ciphertext, plaintext_out, plaintext_len_out);
488 }
489
490 if (!crypto_is_ready(ctx)) {
491 SET_ERRNO(ERROR_CRYPTO, "Crypto context not ready for decryption");
493 }
494
495 // Check minimum ciphertext size (nonce + MAC)
496 size_t min_ciphertext_size = ctx->nonce_size + ctx->mac_size;
497 if (ciphertext_len < min_ciphertext_size) {
498 SET_ERRNO(ERROR_INVALID_PARAM, "Ciphertext too small: %zu < %zu", ciphertext_len, min_ciphertext_size);
500 }
501
502 size_t plaintext_len = ciphertext_len - ctx->nonce_size - ctx->mac_size;
503 if (plaintext_out_size < plaintext_len) {
504 SET_ERRNO(ERROR_BUFFER, "Plaintext buffer too small: %zu < %zu", plaintext_out_size, plaintext_len);
506 }
507
508 // Extract nonce from beginning of ciphertext
509 const uint8_t *nonce = ciphertext;
510 const uint8_t *encrypted_data = ciphertext + ctx->nonce_size;
511
512 // Choose decryption key (prefer shared key over password key)
513 const uint8_t *decryption_key = NULL;
514 if (ctx->key_exchange_complete) {
515 decryption_key = ctx->shared_key;
516 } else if (ctx->has_password) {
517 decryption_key = ctx->password_key;
518 } else {
519 SET_ERRNO(ERROR_CRYPTO, "No decryption key available");
521 }
522
523 // Decrypt using NaCl secretbox (XSalsa20 + Poly1305)
524 if (crypto_secretbox_open_easy(plaintext_out, encrypted_data, ciphertext_len - ctx->nonce_size, nonce,
525 decryption_key) != 0) {
526 SET_ERRNO(ERROR_CRYPTO, "Decryption failed - invalid MAC or corrupted data");
528 }
529
530 *plaintext_len_out = plaintext_len;
531 ctx->bytes_decrypted += plaintext_len;
532
533 return CRYPTO_OK;
534}
535
536// =============================================================================
537// Utility functions
538// =============================================================================
539
541 switch (result) {
542 case CRYPTO_OK:
543 return "Success";
545 return "Initialization failed";
547 return "Invalid parameters";
549 return "Memory allocation failed";
551 return "Libsodium error";
553 return "Key generation failed";
555 return "Password derivation failed";
557 return "Encryption failed";
559 return "Decryption failed";
561 return "Invalid MAC or corrupted data";
563 return "Buffer too small";
565 return "Key exchange not complete";
567 return "Nonce counter exhausted";
569 return "Rekey already in progress";
571 return "Rekey failed";
573 return "Rekey rate limited";
574 default:
575 return "Unknown error";
576 }
577}
578
579void crypto_get_status(const crypto_context_t *ctx, char *status_buffer, size_t buffer_size) {
580 if (!ctx || !status_buffer || buffer_size == 0) {
581 return;
582 }
583
584 if (!ctx->initialized) {
585 SAFE_SNPRINTF(status_buffer, buffer_size, "Not initialized");
586 return;
587 }
588
589 SAFE_SNPRINTF(status_buffer, buffer_size,
590 "Initialized: %s, Password: %s, Key Exchange: %s, Ready: %s, "
591 "Encrypted: %" PRIu64 " bytes, Decrypted: %" PRIu64 " bytes, Nonce: %" PRIu64,
592 ctx->initialized ? "yes" : "no", ctx->has_password ? "yes" : "no",
593 ctx->key_exchange_complete ? "complete" : "incomplete", crypto_is_ready(ctx) ? "yes" : "no",
595}
596
597bool crypto_secure_compare(const uint8_t *lhs, const uint8_t *rhs, size_t len) {
598 if (!lhs || !rhs) {
599 return false;
600 }
601 return sodium_memcmp(lhs, rhs, len) == 0;
602}
603
605 if (!buffer || len == 0) {
606 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_random_bytes: Invalid parameters (buffer=%p, len=%zu)", buffer, len);
608 }
609
610 crypto_result_t result = init_libsodium();
611 if (result != CRYPTO_OK) {
612 return result;
613 }
614
615 randombytes_buf(buffer, len);
616 return CRYPTO_OK;
617}
618
619// =============================================================================
620// Network integration helpers
621// =============================================================================
622
624 size_t *packet_len_out) {
625 if (!ctx || !ctx->initialized || !packet_out || !packet_len_out) {
627 "crypto_create_public_key_packet: Invalid parameters (ctx=%p, initialized=%d, packet_out=%p, "
628 "packet_len_out=%p)",
629 ctx, ctx ? ctx->initialized : 0, packet_out, packet_len_out);
631 }
632
633 size_t required_size = sizeof(uint32_t) + ctx->public_key_size; // type + key
634 if (packet_size < required_size) {
635 SET_ERRNO(ERROR_BUFFER, "crypto_create_public_key_packet: Buffer too small (size=%zu, required=%zu)", packet_size,
636 required_size);
638 }
639
640 // Pack packet: [type:4][public_key:32]
641 uint32_t packet_type = CRYPTO_PACKET_PUBLIC_KEY;
642 SAFE_MEMCPY(packet_out, sizeof(packet_type), &packet_type, sizeof(packet_type));
643 // Bounds check to prevent buffer overflow
644 size_t copy_size = (ctx->public_key_size <= X25519_KEY_SIZE) ? ctx->public_key_size : X25519_KEY_SIZE;
645 SAFE_MEMCPY(packet_out + sizeof(packet_type), copy_size, ctx->public_key, copy_size);
646
647 *packet_len_out = required_size;
648 return CRYPTO_OK;
649}
650
652 if (!ctx || !ctx->initialized || !packet) {
654 "crypto_process_public_key_packet: Invalid parameters (ctx=%p, initialized=%d, packet=%p)", ctx,
655 ctx ? ctx->initialized : 0, packet);
657 }
658
659 size_t expected_size = sizeof(uint32_t) + ctx->public_key_size;
660 if (packet_len != expected_size) {
661 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_process_public_key_packet: Invalid packet size (expected=%zu, got=%zu)",
662 expected_size, packet_len);
664 }
665
666 // Unpack packet: [type:4][public_key:32]
667 uint32_t packet_type;
668 SAFE_MEMCPY(&packet_type, sizeof(packet_type), packet, sizeof(packet_type));
669
670 if (packet_type != CRYPTO_PACKET_PUBLIC_KEY) {
671 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_process_public_key_packet: Invalid packet type (expected=%u, got=%u)",
672 CRYPTO_PACKET_PUBLIC_KEY, packet_type);
674 }
675
676 const uint8_t *peer_public_key = packet + sizeof(packet_type);
677 return crypto_set_peer_public_key(ctx, peer_public_key);
678}
679
681 uint8_t *packet_out, size_t packet_size, size_t *packet_len_out) {
682 if (!ctx || !data || !packet_out || !packet_len_out) {
684 "crypto_create_encrypted_packet: Invalid parameters (ctx=%p, data=%p, packet_out=%p, packet_len_out=%p)",
685 ctx, data, packet_out, packet_len_out);
687 }
688
689 if (!crypto_is_ready(ctx)) {
690 SET_ERRNO(ERROR_CRYPTO, "crypto_create_encrypted_packet: Crypto context not ready");
692 }
693
694 // CRITICAL: Validate data_len to prevent integer overflow in size calculations
695 if (data_len > CRYPTO_MAX_PLAINTEXT_SIZE) {
696 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_create_encrypted_packet: data_len %zu exceeds max %d", data_len,
699 }
700
701 // Check for integer overflow: data_len + nonce_size + mac_size
702 if (data_len > SIZE_MAX - ctx->nonce_size - ctx->mac_size) {
703 SET_ERRNO(ERROR_BUFFER, "crypto_create_encrypted_packet: encrypted_size overflow");
705 }
706 size_t encrypted_size = data_len + ctx->nonce_size + ctx->mac_size;
707
708 // Check for integer overflow: header + encrypted_size
709 size_t header_size = sizeof(uint32_t) + sizeof(uint32_t);
710 if (encrypted_size > SIZE_MAX - header_size) {
711 SET_ERRNO(ERROR_BUFFER, "crypto_create_encrypted_packet: required_size overflow");
713 }
714 size_t required_size = header_size + encrypted_size; // type + len + encrypted_data
715
716 if (packet_size < required_size) {
717 SET_ERRNO(ERROR_BUFFER, "crypto_create_encrypted_packet: Buffer too small (size=%zu, required=%zu)", packet_size,
718 required_size);
720 }
721
722 // Encrypt the data
723 size_t ciphertext_len;
724 uint8_t *encrypted_data = packet_out + sizeof(uint32_t) + sizeof(uint32_t);
725 crypto_result_t result = crypto_encrypt(ctx, data, data_len, encrypted_data,
726 packet_size - sizeof(uint32_t) - sizeof(uint32_t), &ciphertext_len);
727 if (result != CRYPTO_OK) {
728 return result;
729 }
730
731 // Pack packet: [type:4][length:4][encrypted_data:var]
732 uint32_t packet_type = CRYPTO_PACKET_ENCRYPTED_DATA;
733 uint32_t data_length = (uint32_t)ciphertext_len;
734
735 SAFE_MEMCPY(packet_out, sizeof(packet_type), &packet_type, sizeof(packet_type));
736 SAFE_MEMCPY(packet_out + sizeof(packet_type), sizeof(data_length), &data_length, sizeof(data_length));
737
738 *packet_len_out = required_size;
739 return CRYPTO_OK;
740}
741
743 uint8_t *data_out, size_t data_size, size_t *data_len_out) {
744 if (!ctx || !packet || !data_out || !data_len_out) {
746 "crypto_process_encrypted_packet: Invalid parameters (ctx=%p, packet=%p, data_out=%p, data_len_out=%p)",
747 ctx, packet, data_out, data_len_out);
749 }
750
751 if (!crypto_is_ready(ctx)) {
752 SET_ERRNO(ERROR_CRYPTO, "crypto_process_encrypted_packet: Crypto context not ready");
754 }
755
756 if (packet_len < sizeof(uint32_t) + sizeof(uint32_t)) {
757 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_process_encrypted_packet: Packet too small (size=%zu)", packet_len);
759 }
760
761 // Unpack packet: [type:4][length:4][encrypted_data:var]
762 uint32_t packet_type;
763 uint32_t data_length;
764 SAFE_MEMCPY(&packet_type, sizeof(packet_type), packet, sizeof(packet_type));
765 SAFE_MEMCPY(&data_length, sizeof(data_length), packet + sizeof(packet_type), sizeof(data_length));
766
767 if (packet_type != CRYPTO_PACKET_ENCRYPTED_DATA) {
768 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_process_encrypted_packet: Invalid packet type (expected=%u, got=%u)",
769 CRYPTO_PACKET_ENCRYPTED_DATA, packet_type);
771 }
772
773 if (packet_len != sizeof(uint32_t) + sizeof(uint32_t) + data_length) {
774 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_process_encrypted_packet: Packet length mismatch (expected=%zu, got=%zu)",
775 sizeof(uint32_t) + sizeof(uint32_t) + data_length, packet_len);
777 }
778
779 const uint8_t *encrypted_data = packet + sizeof(uint32_t) + sizeof(uint32_t);
780 return crypto_decrypt(ctx, encrypted_data, data_length, data_out, data_size, data_len_out);
781}
782
783// =============================================================================
784// Authentication and handshake functions
785// =============================================================================
786
788 if (!nonce) {
789 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_generate_nonce: NULL nonce buffer");
791 }
792
793 crypto_result_t result = init_libsodium();
794 if (result != CRYPTO_OK) {
795 return result;
796 }
797
798 randombytes_buf(nonce, 32);
799 return CRYPTO_OK;
800}
801
803 uint8_t hmac[32]) {
804 if (!ctx || !key || !data || !hmac) {
805 return SET_ERRNO(ERROR_INVALID_PARAM, "crypto_compute_hmac: Invalid parameters (ctx=%p, key=%p, data=%p, hmac=%p)",
806 ctx, key, data, hmac);
807 }
808
809 crypto_result_t result = init_libsodium();
810 if (result != CRYPTO_OK) {
811 return result;
812 }
813
814 crypto_auth_hmacsha256(hmac, data, 32, key);
815 return CRYPTO_OK;
816}
817
819 size_t data_len, uint8_t hmac[32]) {
820 if (!ctx || !key || !data || !hmac || data_len == 0) {
822 "crypto_compute_hmac_ex: Invalid parameters (ctx=%p, key=%p, data=%p, data_len=%zu, hmac=%p)", ctx,
823 key, data, data_len, hmac);
824 }
825
826 crypto_result_t result = init_libsodium();
827 if (result != CRYPTO_OK) {
828 return result;
829 }
830
831 crypto_auth_hmacsha256(hmac, data, data_len, key);
832 return CRYPTO_OK;
833}
834
835bool crypto_verify_hmac(const uint8_t key[32], const uint8_t data[32], const uint8_t expected_hmac[32]) {
836 if (!key || !data || !expected_hmac) {
837 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_verify_hmac: Invalid parameters (key=%p, data=%p, expected_hmac=%p)", key,
838 data, expected_hmac);
839 return false;
840 }
841
842 uint8_t computed_hmac[32];
843 if (crypto_auth_hmacsha256(computed_hmac, data, 32, key) != 0) {
844 return false;
845 }
846
847 return sodium_memcmp(computed_hmac, expected_hmac, 32) == 0;
848}
849
850bool crypto_verify_hmac_ex(const uint8_t key[32], const uint8_t *data, size_t data_len,
851 const uint8_t expected_hmac[32]) {
852 if (!key || !data || !expected_hmac || data_len == 0) {
854 "crypto_verify_hmac_ex: Invalid parameters (key=%p, data=%p, data_len=%zu, expected_hmac=%p)", key, data,
855 data_len, expected_hmac);
856 return false;
857 }
858
859 uint8_t computed_hmac[32];
860 if (crypto_auth_hmacsha256(computed_hmac, data, data_len, key) != 0) {
861 return false;
862 }
863
864 return sodium_memcmp(computed_hmac, expected_hmac, 32) == 0;
865}
866
867// =============================================================================
868// High-level authentication helpers (shared between client and server)
869// =============================================================================
870
872 uint8_t hmac_out[32]) {
873 if (!ctx || !nonce || !hmac_out) {
874 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_compute_auth_response: Invalid parameters (ctx=%p, nonce=%p, hmac_out=%p)",
875 ctx, nonce, hmac_out);
877 }
878
879 // Ensure shared secret is derived before computing HMAC
880 // This is critical for password HMAC which binds to the shared secret
881 if (!ctx->key_exchange_complete) {
882 SET_ERRNO(ERROR_CRYPTO, "Cannot compute auth response - key exchange not complete");
884 }
885
886 // Bind password HMAC to DH shared_secret to prevent MITM
887 // Combined data: nonce || shared_secret
888 uint8_t combined_data[64];
889 memcpy(combined_data, nonce, 32);
890 memcpy(combined_data + 32, ctx->shared_key, 32);
891
892 // Use password_key if available, otherwise use shared_key
893 const uint8_t *auth_key = ctx->has_password ? ctx->password_key : ctx->shared_key;
894
895 crypto_result_t result = crypto_compute_hmac_ex(ctx, auth_key, combined_data, 64, hmac_out);
896
897 // Securely zero sensitive data containing shared secret
898 sodium_memzero(combined_data, sizeof(combined_data));
899
900 return result;
901}
902
903bool crypto_verify_auth_response(const crypto_context_t *ctx, const uint8_t nonce[32],
904 const uint8_t expected_hmac[32]) {
905 if (!ctx || !nonce || !expected_hmac) {
907 "crypto_verify_auth_response: Invalid parameters (ctx=%p, nonce=%p, expected_hmac=%p)", ctx, nonce,
908 expected_hmac);
909 return false;
910 }
911
912 // Ensure shared secret is derived before verifying HMAC
913 // This is critical for password HMAC verification which binds to the shared secret
914 if (!ctx->key_exchange_complete) {
915 SET_ERRNO(ERROR_CRYPTO, "Cannot verify auth response - key exchange not complete");
916 return false;
917 }
918
919 // Bind password HMAC to DH shared_secret to prevent MITM
920 // Combined data: nonce || shared_secret
921 uint8_t combined_data[64];
922 memcpy(combined_data, nonce, 32);
923 memcpy(combined_data + 32, ctx->shared_key, 32);
924
925 // Use password_key if available, otherwise use shared_key
926 const uint8_t *auth_key = ctx->has_password ? ctx->password_key : ctx->shared_key;
927
928 log_debug("Verifying auth response: has_password=%d, key_exchange_complete=%d, using_password_key=%d",
929 ctx->has_password, ctx->key_exchange_complete, (auth_key == ctx->password_key));
930
931 bool result = crypto_verify_hmac_ex(auth_key, combined_data, 64, expected_hmac);
932
933 // Securely zero sensitive data containing shared secret
934 sodium_memzero(combined_data, sizeof(combined_data));
935
936 return result;
937}
938
939crypto_result_t crypto_create_auth_challenge(const crypto_context_t *ctx, uint8_t *packet_out, size_t packet_size,
940 size_t *packet_len_out) {
941 if (!ctx || !ctx->initialized || !packet_out || !packet_len_out) {
942 SET_ERRNO(
944 "crypto_create_auth_challenge: Invalid parameters (ctx=%p, initialized=%d, packet_out=%p, packet_len_out=%p)",
945 ctx, ctx ? ctx->initialized : 0, packet_out, packet_len_out);
947 }
948
949 size_t required_size = sizeof(uint32_t) + ctx->auth_challenge_size; // type + nonce
950 if (packet_size < required_size) {
951 SET_ERRNO(ERROR_BUFFER, "crypto_create_auth_challenge: Buffer too small (size=%zu, required=%zu)", packet_size,
952 required_size);
954 }
955
956 // Generate random nonce (stores result in ctx->auth_nonce)
958 if (result != CRYPTO_OK) {
959 return result;
960 }
961
962 // Pack packet: [type:4][nonce:auth_challenge_size]
963 uint32_t packet_type = CRYPTO_PACKET_AUTH_CHALLENGE;
964 SAFE_MEMCPY(packet_out, sizeof(packet_type), &packet_type, sizeof(packet_type));
965 SAFE_MEMCPY(packet_out + sizeof(packet_type), ctx->auth_challenge_size, ctx->auth_nonce, ctx->auth_challenge_size);
966
967 *packet_len_out = required_size;
968 return CRYPTO_OK;
969}
970
972 if (!ctx || !ctx->initialized || !packet) {
974 "crypto_process_auth_challenge: Invalid parameters (ctx=%p, initialized=%d, packet=%p)", ctx,
975 ctx ? ctx->initialized : 0, packet);
977 }
978
979 size_t expected_size = sizeof(uint32_t) + ctx->auth_challenge_size; // type + nonce
980 if (packet_len != expected_size) {
981 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_process_auth_challenge: Invalid packet size (expected=%zu, got=%zu)",
982 expected_size, packet_len);
984 }
985
986 // Unpack packet: [type:4][nonce:auth_challenge_size]
987 uint32_t packet_type;
988 SAFE_MEMCPY(&packet_type, sizeof(packet_type), packet, sizeof(packet_type));
989
990 if (packet_type != CRYPTO_PACKET_AUTH_CHALLENGE) {
991 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_process_auth_challenge: Invalid packet type (expected=%u, got=%u)",
992 CRYPTO_PACKET_AUTH_CHALLENGE, packet_type);
994 }
995
996 // Store the nonce for HMAC computation (use buffer size for memcpy size, actual size from context)
997 SAFE_MEMCPY(ctx->auth_nonce, sizeof(ctx->auth_nonce), packet + sizeof(packet_type), ctx->auth_challenge_size);
998
999 log_debug("Auth challenge received and processed");
1000 return CRYPTO_OK;
1001}
1002
1004 if (!ctx || !ctx->initialized || !packet) {
1006 "crypto_process_auth_response: Invalid context or packet (ctx=%p, initialized=%d, packet=%p)", ctx,
1007 ctx ? ctx->initialized : 0, packet);
1009 }
1010
1011 size_t expected_size = sizeof(uint32_t) + 32; // type + hmac
1012 if (packet_len != expected_size) {
1013 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_process_auth_response: Invalid packet size (expected=%zu, got=%zu)",
1014 expected_size, packet_len);
1016 }
1017
1018 // Unpack packet: [type:4][hmac:32]
1019 uint32_t packet_type;
1020 SAFE_MEMCPY(&packet_type, sizeof(packet_type), packet, sizeof(packet_type));
1021
1022 if (packet_type != CRYPTO_PACKET_AUTH_RESPONSE) {
1023 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_process_auth_response: Invalid packet type (expected=0x%x, got=0x%x)",
1024 CRYPTO_PACKET_AUTH_RESPONSE, packet_type);
1026 }
1027
1028 const uint8_t *received_hmac = packet + sizeof(packet_type);
1029
1030 // Verify HMAC using shared secret
1031 if (!crypto_verify_hmac(ctx->shared_key, ctx->auth_nonce, received_hmac)) {
1032 SET_ERRNO(ERROR_CRYPTO, "crypto_process_auth_response: HMAC verification failed");
1034 }
1035
1036 ctx->handshake_complete = true;
1037 log_debug("Authentication successful - handshake complete");
1038 return CRYPTO_OK;
1039}
1040
1041// =============================================================================
1042// Shared Cryptographic Operations
1043// =============================================================================
1044
1046 const uint8_t *shared_secret, uint8_t *hmac_out) {
1047 if (!ctx || !password_key || !nonce || !shared_secret || !hmac_out) {
1049 "Invalid parameters: ctx=%p, password_key=%p, nonce=%p, shared_secret=%p, hmac_out=%p", ctx,
1050 password_key, nonce, shared_secret, hmac_out);
1051 }
1052
1053 // Combine nonce and shared_secret for HMAC computation
1054 // This binds the password to the DH shared secret, preventing MITM attacks
1055 uint8_t combined_data[64];
1056 memcpy(combined_data, nonce, 32);
1057 memcpy(combined_data + 32, shared_secret, 32);
1058
1059 // Compute HMAC using the password-derived key
1060 if (crypto_compute_hmac_ex(ctx, password_key, combined_data, 64, hmac_out) != 0) {
1061 // Securely zero sensitive data containing shared secret even on error
1062 sodium_memzero(combined_data, sizeof(combined_data));
1063 return SET_ERRNO(ERROR_CRYPTO, "Failed to compute password HMAC");
1064 }
1065
1066 // Securely zero sensitive data containing shared secret
1067 sodium_memzero(combined_data, sizeof(combined_data));
1068
1069 return ASCIICHAT_OK;
1070}
1071
1072asciichat_error_t crypto_verify_peer_signature(const uint8_t *peer_public_key, const uint8_t *ephemeral_key,
1073 size_t ephemeral_key_size, const uint8_t *signature) {
1074 if (!peer_public_key || !ephemeral_key || !signature) {
1075 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: peer_public_key=%p, ephemeral_key=%p, signature=%p",
1076 peer_public_key, ephemeral_key, signature);
1077 }
1078
1079 if (ephemeral_key_size == 0) {
1080 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid ephemeral key size: %zu", ephemeral_key_size);
1081 }
1082
1083 // Verify the signature using Ed25519
1084 if (crypto_sign_verify_detached(signature, ephemeral_key, ephemeral_key_size, peer_public_key) != 0) {
1085 return SET_ERRNO(ERROR_CRYPTO, "Peer signature verification failed");
1086 }
1087
1088 return ASCIICHAT_OK;
1089}
1090
1091asciichat_error_t crypto_sign_ephemeral_key(const private_key_t *private_key, const uint8_t *ephemeral_key,
1092 size_t ephemeral_key_size, uint8_t *signature_out) {
1093 if (!private_key || !ephemeral_key || !signature_out) {
1094 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: private_key=%p, ephemeral_key=%p, signature_out=%p",
1095 private_key, ephemeral_key, signature_out);
1096 }
1097
1098 if (ephemeral_key_size == 0) {
1099 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid ephemeral key size: %zu", ephemeral_key_size);
1100 return ERROR_INVALID_PARAM;
1101 }
1102
1103 // Sign the ephemeral key with our Ed25519 private key
1104 if (private_key->type == KEY_TYPE_ED25519) {
1105 if (crypto_sign_detached(signature_out, NULL, ephemeral_key, ephemeral_key_size, private_key->key.ed25519) != 0) {
1106 return SET_ERRNO(ERROR_CRYPTO, "Failed to sign ephemeral key");
1107 }
1108 } else {
1109 return SET_ERRNO(ERROR_CRYPTO, "Unsupported private key type for signing");
1110 }
1111
1112 return ASCIICHAT_OK;
1113}
1114
1115void crypto_combine_auth_data(const uint8_t *hmac, const uint8_t *challenge_nonce, uint8_t *combined_out) {
1116 if (!hmac || !challenge_nonce || !combined_out) {
1117 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: hmac=%p, challenge_nonce=%p, combined_out=%p", hmac,
1118 challenge_nonce, combined_out);
1119 return;
1120 }
1121
1122 // Combine HMAC (32 bytes) + challenge nonce (32 bytes) = 64 bytes total
1123 memcpy(combined_out, hmac, 32);
1124 memcpy(combined_out + 32, challenge_nonce, 32);
1125}
1126
1127void crypto_extract_auth_data(const uint8_t *combined_data, uint8_t *hmac_out, uint8_t *challenge_out) {
1128 if (!combined_data || !hmac_out || !challenge_out) {
1129 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: combined_data=%p, hmac_out=%p, challenge_out=%p", combined_data,
1130 hmac_out, challenge_out);
1131 return;
1132 }
1133
1134 // Extract HMAC (first 32 bytes) and challenge nonce (last 32 bytes)
1135 memcpy(hmac_out, combined_data, 32);
1136 memcpy(challenge_out, combined_data + 32, 32);
1137}
1138
1139// =============================================================================
1140// Session Rekeying Protocol Implementation
1141// =============================================================================
1142
1144 if (!ctx || !ctx->initialized) {
1145 return false;
1146 }
1147
1148 // Don't trigger rekey if one is already in progress
1149 if (ctx->rekey_in_progress) {
1150 return false;
1151 }
1152
1153 // Don't trigger rekey if handshake isn't complete yet
1154 if (!ctx->handshake_complete) {
1155 return false;
1156 }
1157
1158 // Check packet count threshold
1159 if (ctx->rekey_packet_count >= ctx->rekey_packet_threshold) {
1160 log_debug("Rekey triggered: packet count (%llu) >= threshold (%llu)", (unsigned long long)ctx->rekey_packet_count,
1161 (unsigned long long)ctx->rekey_packet_threshold);
1162 return true;
1163 }
1164
1165 // Check time threshold
1166 time_t now = time(NULL);
1167 time_t elapsed = now - ctx->rekey_last_time;
1168 if (elapsed >= ctx->rekey_time_threshold) {
1169 log_debug("Rekey triggered: time elapsed (%ld sec) >= threshold (%ld sec)", (long)elapsed,
1170 (long)ctx->rekey_time_threshold);
1171 return true;
1172 }
1173
1174 return false;
1175}
1176
1178 if (!ctx || !ctx->initialized) {
1179 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_rekey_init: Invalid context");
1181 }
1182
1183 if (ctx->rekey_in_progress) {
1184 SET_ERRNO(ERROR_CRYPTO, "Rekey already in progress");
1186 }
1187
1188 // Rate limiting: check minimum interval since last rekey
1189 time_t now = time(NULL);
1190 time_t since_last_rekey = now - ctx->rekey_last_time;
1191 if (since_last_rekey < REKEY_MIN_INTERVAL) {
1192 char elapsed_str[32], min_str[32];
1193 format_duration_s((double)since_last_rekey, elapsed_str, sizeof(elapsed_str));
1194 format_duration_s((double)REKEY_MIN_INTERVAL, min_str, sizeof(min_str));
1195 log_warn("Rekey rate limited: %s since last rekey (minimum: %s)", elapsed_str, min_str);
1197 }
1198
1199 // Check if too many consecutive failures
1201 log_error("Too many consecutive rekey failures (%d), giving up", ctx->rekey_failure_count);
1203 }
1204
1205 // Generate new ephemeral X25519 keypair for rekeying
1206 if (crypto_box_keypair(ctx->temp_public_key, ctx->temp_private_key) != 0) {
1207 SET_ERRNO(ERROR_CRYPTO, "Failed to generate rekey ephemeral keypair");
1209 }
1210
1211 ctx->rekey_in_progress = true;
1212 ctx->has_temp_key = true;
1213
1214 log_info("Rekey initiated (packets: %llu, time elapsed: %ld sec, attempt %d)",
1215 (unsigned long long)ctx->rekey_packet_count, (long)since_last_rekey, ctx->rekey_failure_count + 1);
1216
1217 return CRYPTO_OK;
1218}
1219
1221 if (!ctx || !ctx->initialized || !peer_new_public_key) {
1222 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_rekey_process_request: Invalid parameters");
1224 }
1225
1226 // Generate our own new ephemeral keypair (responder side)
1227 if (crypto_box_keypair(ctx->temp_public_key, ctx->temp_private_key) != 0) {
1228 SET_ERRNO(ERROR_CRYPTO, "Failed to generate rekey ephemeral keypair");
1230 }
1231
1232 // Compute new shared secret: DH(our_new_private_key, peer_new_public_key)
1233 if (crypto_box_beforenm(ctx->temp_shared_key, peer_new_public_key, ctx->temp_private_key) != 0) {
1234 SET_ERRNO(ERROR_CRYPTO, "Failed to compute rekey shared secret");
1235 secure_memzero(ctx->temp_private_key, sizeof(ctx->temp_private_key));
1236 secure_memzero(ctx->temp_public_key, sizeof(ctx->temp_public_key));
1238 }
1239
1240 ctx->rekey_in_progress = true;
1241 ctx->has_temp_key = true;
1242
1243 log_debug("Rekey request processed (responder side), new shared secret computed");
1244
1245 return CRYPTO_OK;
1246}
1247
1249 if (!ctx || !ctx->initialized || !peer_new_public_key) {
1250 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_rekey_process_response: Invalid parameters");
1252 }
1253
1254 if (!ctx->rekey_in_progress || !ctx->has_temp_key) {
1255 SET_ERRNO(ERROR_CRYPTO, "No rekey in progress");
1257 }
1258
1259 // Compute new shared secret: DH(our_new_private_key, peer_new_public_key)
1260 if (crypto_box_beforenm(ctx->temp_shared_key, peer_new_public_key, ctx->temp_private_key) != 0) {
1261 SET_ERRNO(ERROR_CRYPTO, "Failed to compute rekey shared secret");
1262 crypto_rekey_abort(ctx);
1264 }
1265
1266 log_debug("Rekey response processed (initiator side), new shared secret computed");
1267
1268 return CRYPTO_OK;
1269}
1270
1272 if (!ctx || !ctx->initialized) {
1273 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_rekey_commit: Invalid context");
1275 }
1276
1277 if (!ctx->rekey_in_progress || !ctx->has_temp_key) {
1278 SET_ERRNO(ERROR_CRYPTO, "No rekey in progress to commit");
1280 }
1281
1282 // Wipe old shared secret securely
1283 secure_memzero(ctx->shared_key, sizeof(ctx->shared_key));
1284
1285 // Switch to new shared secret
1286 SAFE_MEMCPY(ctx->shared_key, sizeof(ctx->shared_key), ctx->temp_shared_key, sizeof(ctx->temp_shared_key));
1287
1288 // Reset nonce counter to 1 (fresh start with new key)
1289 ctx->nonce_counter = 1;
1290
1291 // Generate new session ID to prevent cross-session replay
1292 randombytes_buf(ctx->session_id, sizeof(ctx->session_id));
1293
1294 // Reset rekey tracking
1295 ctx->rekey_packet_count = 0;
1296 ctx->rekey_last_time = time(NULL);
1297 ctx->rekey_count++;
1298
1299 // Clear rekey state
1300 secure_memzero(ctx->temp_public_key, sizeof(ctx->temp_public_key));
1301 secure_memzero(ctx->temp_private_key, sizeof(ctx->temp_private_key));
1302 secure_memzero(ctx->temp_shared_key, sizeof(ctx->temp_shared_key));
1303 ctx->has_temp_key = false;
1304 ctx->rekey_in_progress = false;
1305
1306 // Reset failure counter on successful rekey
1307 ctx->rekey_failure_count = 0;
1308
1309 log_info("Rekey committed successfully (rekey #%llu, nonce reset to 1, new session_id generated)",
1310 (unsigned long long)ctx->rekey_count);
1311
1312 return CRYPTO_OK;
1313}
1314
1316 if (!ctx) {
1317 return;
1318 }
1319
1320 log_warn("Aborting rekey (attempt %d failed), keeping old encryption key", ctx->rekey_failure_count + 1);
1321
1322 // Wipe temporary keys securely
1323 secure_memzero(ctx->temp_public_key, sizeof(ctx->temp_public_key));
1324 secure_memzero(ctx->temp_private_key, sizeof(ctx->temp_private_key));
1325 secure_memzero(ctx->temp_shared_key, sizeof(ctx->temp_shared_key));
1326
1327 // Reset rekey state
1328 ctx->has_temp_key = false;
1329 ctx->rekey_in_progress = false;
1330
1331 // Increment failure counter for exponential backoff
1332 ctx->rekey_failure_count++;
1333
1334 // Continue using old key - no disruption to connection
1335}
1336
1337void crypto_get_rekey_status(const crypto_context_t *ctx, char *status_buffer, size_t buffer_size) {
1338 if (!ctx || !status_buffer || buffer_size == 0) {
1339 return;
1340 }
1341
1342 time_t now = time(NULL);
1343 time_t elapsed = now - ctx->rekey_last_time;
1344 time_t remaining_time = (ctx->rekey_time_threshold > elapsed) ? (ctx->rekey_time_threshold - elapsed) : 0;
1345 uint64_t remaining_packets = (ctx->rekey_packet_threshold > ctx->rekey_packet_count)
1347 : 0;
1348
1349 snprintf(status_buffer, buffer_size,
1350 "Rekey status: %s | "
1351 "Packets: %llu/%llu (%llu remaining) | "
1352 "Time: %ld/%ld sec (%ld sec remaining) | "
1353 "Rekeys: %llu | Failures: %d",
1354 ctx->rekey_in_progress ? "IN_PROGRESS" : "IDLE", (unsigned long long)ctx->rekey_packet_count,
1355 (unsigned long long)ctx->rekey_packet_threshold, (unsigned long long)remaining_packets, (long)elapsed,
1356 (long)ctx->rekey_time_threshold, (long)remaining_time, (unsigned long long)ctx->rekey_count,
1357 ctx->rekey_failure_count);
1358}
⚠️‼️ Error and/or exit() when things go bad.
unsigned int uint32_t
Definition common.h:58
#define SAFE_SNPRINTF(buffer, buffer_size,...)
Definition common.h:412
unsigned long long uint64_t
Definition common.h:59
unsigned char uint8_t
Definition common.h:56
#define SAFE_MEMCPY(dest, dest_size, src, count)
Definition common.h:388
crypto_result_t crypto_rekey_commit(crypto_context_t *ctx)
Commit to new keys after successful REKEY_COMPLETE.
crypto_result_t crypto_create_auth_challenge(const crypto_context_t *ctx, uint8_t *packet_out, size_t packet_size, size_t *packet_len_out)
Create authentication challenge packet.
crypto_result_t crypto_process_public_key_packet(crypto_context_t *ctx, const uint8_t *packet, size_t packet_len)
Process received public key packet from peer.
void crypto_cleanup(crypto_context_t *ctx)
Cleanup crypto context with secure memory wiping.
crypto_result_t crypto_compute_hmac_ex(const crypto_context_t *ctx, const uint8_t key[32], const uint8_t *data, size_t data_len, uint8_t hmac[32])
Compute HMAC-SHA256 for variable-length data.
#define REKEY_MIN_INTERVAL
Minimum time interval between rekey requests (3 seconds for testing, 60 for production)
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)
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)
Encrypt data using XSalsa20-Poly1305.
#define ED25519_SIGNATURE_SIZE
Ed25519 signature size in bytes.
#define AUTH_CHALLENGE_SIZE
Challenge nonce size (32 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.
crypto_result_t crypto_init(crypto_context_t *ctx)
Initialize libsodium and crypto context.
#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.
asciichat_error_t crypto_compute_password_hmac(crypto_context_t *ctx, const uint8_t *password_key, const uint8_t *nonce, const uint8_t *shared_secret, uint8_t *hmac_out)
Compute password-based HMAC 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.
#define REKEY_DEFAULT_TIME_THRESHOLD
Default rekey time threshold (1 hour in seconds)
#define REKEY_MAX_FAILURE_COUNT
Maximum consecutive rekey failures before giving up.
#define SECRETBOX_KEY_SIZE
Secretbox key size in bytes.
asciichat_error_t crypto_verify_peer_signature(const uint8_t *peer_public_key, const uint8_t *ephemeral_key, size_t ephemeral_key_size, const uint8_t *signature)
Verify peer's signature on ephemeral key.
bool crypto_verify_hmac_ex(const uint8_t key[32], const uint8_t *data, size_t data_len, const uint8_t expected_hmac[32])
Verify HMAC-SHA256 for variable-length data.
bool crypto_verify_password(const crypto_context_t *ctx, const char *password)
Verify password matches stored salt/key.
crypto_result_t crypto_get_public_key(const crypto_context_t *ctx, uint8_t *public_key_out)
Get public key for sending to peer (step 1 of handshake)
crypto_result_t crypto_create_encrypted_packet(crypto_context_t *ctx, const uint8_t *data, size_t data_len, uint8_t *packet_out, size_t packet_size, size_t *packet_len_out)
Create encrypted data packet for network transmission.
void crypto_get_rekey_status(const crypto_context_t *ctx, char *status_buffer, size_t buffer_size)
Get the current rekeying state for debugging/logging.
bool crypto_should_rekey(const crypto_context_t *ctx)
Check if rekeying should be triggered based on time or packet count thresholds.
crypto_result_t crypto_process_auth_response(crypto_context_t *ctx, const uint8_t *packet, size_t packet_len)
Process authentication response packet.
crypto_result_t crypto_generate_keypair(crypto_context_t *ctx)
Generate new X25519 key pair for key exchange.
#define MIN_PASSWORD_LENGTH
Minimum password length (8 characters)
void crypto_combine_auth_data(const uint8_t *hmac, const uint8_t *challenge_nonce, uint8_t *combined_out)
Combine HMAC and challenge nonce for transmission.
bool crypto_verify_hmac(const uint8_t key[32], const uint8_t data[32], const uint8_t expected_hmac[32])
Verify HMAC-SHA256 for fixed 32-byte data.
crypto_result_t crypto_process_auth_challenge(crypto_context_t *ctx, const uint8_t *packet, size_t packet_len)
Process authentication challenge packet.
#define X25519_KEY_SIZE
X25519 key size in bytes.
void crypto_extract_auth_data(const uint8_t *combined_data, uint8_t *hmac_out, uint8_t *challenge_out)
Extract HMAC and challenge nonce from combined data.
#define MAX_PASSWORD_LENGTH
Maximum password length (256 characters)
void crypto_get_status(const crypto_context_t *ctx, char *status_buffer, size_t buffer_size)
Get crypto context status information for debugging.
void crypto_rekey_abort(crypto_context_t *ctx)
Abort rekeying and fallback to old keys.
bool crypto_is_ready(const crypto_context_t *ctx)
Check if key exchange is complete and ready for encryption.
crypto_result_t crypto_validate_password(const char *password)
Validate password length requirements.
crypto_result_t crypto_rekey_process_request(crypto_context_t *ctx, const uint8_t *peer_new_public_key)
Process REKEY_REQUEST from peer (responder side)
#define XSALSA20_NONCE_SIZE
XSalsa20 nonce size in bytes.
asciichat_error_t crypto_sign_ephemeral_key(const private_key_t *private_key, const uint8_t *ephemeral_key, size_t ephemeral_key_size, uint8_t *signature_out)
Sign ephemeral key with private key.
bool crypto_secure_compare(const uint8_t *lhs, const uint8_t *rhs, size_t len)
Secure constant-time comparison of byte arrays.
#define REKEY_TEST_PACKET_THRESHOLD
Test mode rekey packet threshold (1000 packets)
crypto_result_t crypto_compute_hmac(crypto_context_t *ctx, const uint8_t key[32], const uint8_t data[32], uint8_t hmac[32])
Compute HMAC-SHA256 for fixed 32-byte data.
crypto_result_t crypto_derive_password_key(crypto_context_t *ctx, const char *password)
Derive key from password using Argon2id.
#define ARGON2ID_SALT_SIZE
Argon2id salt size in bytes.
#define REKEY_TEST_TIME_THRESHOLD
Test mode rekey time threshold (30 seconds)
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)
Decrypt data using XSalsa20-Poly1305.
crypto_result_t crypto_derive_password_encryption_key(const char *password, uint8_t encryption_key[32])
Derive deterministic encryption key from password for handshake.
crypto_result_t crypto_init_with_password(crypto_context_t *ctx, const char *password)
Initialize with password-based encryption.
crypto_result_t crypto_create_public_key_packet(const crypto_context_t *ctx, uint8_t *packet_out, size_t packet_size, size_t *packet_len_out)
Create public key packet for network transmission.
#define REKEY_DEFAULT_PACKET_THRESHOLD
Default rekey packet threshold (1 million packets)
crypto_result_t crypto_rekey_init(crypto_context_t *ctx)
Initiate rekeying by generating new ephemeral keys.
crypto_result_t crypto_random_bytes(uint8_t *buffer, size_t len)
Generate cryptographically secure random bytes.
crypto_result_t crypto_process_encrypted_packet(crypto_context_t *ctx, const uint8_t *packet, size_t packet_len, uint8_t *data_out, size_t data_size, size_t *data_len_out)
Process received encrypted packet from peer.
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.
crypto_result_t crypto_rekey_process_response(crypto_context_t *ctx, const uint8_t *peer_new_public_key)
Process REKEY_RESPONSE from peer (initiator side)
#define POLY1305_MAC_SIZE
Poly1305 MAC size in bytes.
#define CRYPTO_MAX_PLAINTEXT_SIZE
Maximum plaintext size (1MB)
@ CRYPTO_ERROR_REKEY_IN_PROGRESS
@ CRYPTO_ERROR_KEY_GENERATION
@ CRYPTO_ERROR_REKEY_RATE_LIMITED
@ CRYPTO_ERROR_PASSWORD_DERIVATION
@ CRYPTO_ERROR_INVALID_MAC
@ CRYPTO_ERROR_DECRYPTION
@ CRYPTO_ERROR_ENCRYPTION
@ CRYPTO_OK
@ CRYPTO_ERROR_MEMORY
@ CRYPTO_ERROR_KEY_EXCHANGE_INCOMPLETE
@ CRYPTO_ERROR_REKEY_FAILED
@ CRYPTO_ERROR_LIBSODIUM
@ CRYPTO_ERROR_INVALID_PARAMS
@ CRYPTO_ERROR_BUFFER_TOO_SMALL
@ CRYPTO_ERROR_NONCE_EXHAUSTED
@ CRYPTO_ERROR_INIT_FAILED
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:46
@ ASCIICHAT_OK
Definition error_codes.h:48
@ ERROR_CRYPTO
Definition error_codes.h:88
@ ERROR_INVALID_PARAM
@ ERROR_BUFFER
Definition error_codes.h:96
key_type_t type
Definition key_types.h:92
uint8_t ed25519[64]
Definition key_types.h:94
union private_key_t::@1 key
@ KEY_TYPE_ED25519
Definition key_types.h:52
#define log_warn(...)
Log a WARN message.
#define log_error(...)
Log an ERROR message.
#define log_info(...)
Log an INFO message.
#define log_debug(...)
Log a DEBUG message.
int format_duration_s(double seconds, char *buffer, size_t buffer_size)
Format seconds as human-readable duration string.
Definition time.c:275
Server cryptographic operations and per-client handshake management.
Cryptographic context structure.
uint8_t temp_public_key[32]
uint8_t temp_private_key[32]
uint64_t rekey_packet_threshold
uint8_t peer_public_key[32]
uint8_t temp_shared_key[32]
uint8_t password_salt[32]
Private key structure (for server –ssh-key)
Definition key_types.h:91
Test environment detection utilities.
⏱️ High-precision timing utilities using sokol_time.h and uthash