ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
lib/crypto/crypto.c
Go to the documentation of this file.
1
8#include <ascii-chat/common.h>
14
15#include <string.h>
16#include <time.h>
17#include <inttypes.h>
18#include <ascii-chat/atomic.h>
21
22// Lifecycle state for libsodium initialization
23static lifecycle_t g_libsodium_lc = LIFECYCLE_INIT;
24
25// Internal packet type constants (for test helper functions only)
26// These are NOT part of the main network protocol - they're used for simple
27// packet serialization in crypto module test utilities
28static const uint32_t CRYPTO_PACKET_PUBLIC_KEY = 100;
29static const uint32_t CRYPTO_PACKET_ENCRYPTED_DATA = 102;
30static const uint32_t CRYPTO_PACKET_AUTH_CHALLENGE = 103;
31static const uint32_t CRYPTO_PACKET_AUTH_RESPONSE = 104;
32
33// =============================================================================
34// Internal helper functions
35// =============================================================================
36
37// Initialize libsodium (thread-safe, exactly-once via DCL pattern)
38static crypto_result_t init_libsodium(void) {
39 if (!lifecycle_init_once(&g_libsodium_lc)) {
40 return CRYPTO_OK; // Already initialized or in progress
41 }
42
43 // We won the initialization race
44 if (sodium_init() < 0) {
45 lifecycle_init_abort(&g_libsodium_lc);
46 SET_ERRNO(ERROR_CRYPTO, "Failed to initialize libsodium");
48 }
49
50 lifecycle_init_commit(&g_libsodium_lc);
51 return CRYPTO_OK;
52}
53
54// Generate secure nonce with session ID and counter to prevent reuse
55static void generate_nonce(crypto_context_t *ctx, uint8_t *nonce_out) {
56 // Nonce format (dynamic size based on algorithm):
57 // - Bytes 0-15: Session ID (constant per connection, random across connections)
58 // - Bytes 16+: Counter (increments per packet, prevents reuse within session)
59 //
60 // This prevents replay attacks both within and across sessions:
61 // - Different session_id prevents cross-session replay
62 // - Counter prevents within-session replay
63
64 SAFE_MEMCPY(nonce_out, 16, ctx->session_id, 16);
65
66 // Counter MUST be in big-endian (network byte order) for cross-platform compatibility
67 // between browser (JavaScript/WASM) and native (C) implementations
68 uint64_t counter = ctx->nonce_counter++;
69 uint64_t counter_be = HOST_TO_NET_U64(counter); // Convert to big-endian
70 size_t counter_size = ctx->nonce_size - 16;
71 SAFE_MEMCPY(nonce_out + 16, counter_size, &counter_be,
72 (counter_size < sizeof(counter_be)) ? counter_size : sizeof(counter_be));
73}
74
75// Securely clear memory
76static void secure_memzero(void *ptr, size_t len) {
77 sodium_memzero(ptr, len);
78}
79
80// =============================================================================
81// Core initialization and setup
82// =============================================================================
83
85 log_info("DEBUG: crypto_init START");
86 if (!ctx) {
87 log_info("DEBUG: crypto_init ctx is NULL");
88 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: ctx=%p", ctx);
90 }
91
92 // Initialize libsodium
93 log_info("DEBUG: crypto_init calling init_libsodium");
94 crypto_result_t result = init_libsodium();
95 if (result != CRYPTO_OK) {
96 return result;
97 }
98
99 // Clear context
100 secure_memzero(ctx, sizeof(crypto_context_t));
101
102 // Generate key pair for X25519 key exchange
103 result = crypto_generate_keypair(ctx);
104 if (result != CRYPTO_OK) {
105 return result;
106 }
107
108 ctx->initialized = true;
109 ctx->has_password = false;
110 ctx->key_exchange_complete = false;
111 ctx->peer_key_received = false;
112 ctx->handshake_complete = false;
113 ctx->encrypt_data = true; // Default: enable payload encryption
114 ctx->nonce_counter = 1; // Start from 1 (0 reserved for testing)
115 ctx->bytes_encrypted = 0;
116 ctx->bytes_decrypted = 0;
117
118 // Set default algorithm-specific parameters (can be overridden during handshake)
128 // signature_size and auth_public_key_size remain 0 until set by CRYPTO_PARAMETERS
129 // (must be 0 for unauthenticated format detection in crypto_handshake_client_key_exchange)
130
131 // Generate unique session ID to prevent replay attacks across connections
132 randombytes_buf(ctx->session_id, sizeof(ctx->session_id));
133
134 // Initialize rekeying state
135 ctx->rekey_packet_count = 0;
136 ctx->rekey_last_time = time(NULL); // Initialize to current time
137 ctx->rekey_last_request_time = 0; // No rekey request yet
138 ctx->rekey_in_progress = false;
139 ctx->rekey_failure_count = 0;
140 ctx->has_temp_key = false;
141 ctx->rekey_count = 0;
142
143 // SECURITY: Use production-safe rekey thresholds by default
144 // Rekey every 1 hour OR 1 million packets (whichever comes first)
145 // Only use test mode if explicitly requested via environment variable
146 if (is_test_environment()) {
149 char duration_str[32];
150 time_pretty(ctx->rekey_time_threshold, -1, duration_str, sizeof(duration_str));
151 log_dev("Crypto context initialized with X25519 key exchange (TEST MODE rekey thresholds: %llu packets, %s)",
152 (unsigned long long)ctx->rekey_packet_threshold, duration_str);
153 } else {
154 ctx->rekey_packet_threshold = REKEY_DEFAULT_PACKET_THRESHOLD; // 1 million packets
155 ctx->rekey_time_threshold = REKEY_DEFAULT_TIME_THRESHOLD; // already in nanoseconds (3600 * NS_PER_SEC_INT)
156 char duration_str[32];
157 time_pretty(ctx->rekey_time_threshold, -1, duration_str, sizeof(duration_str));
158 log_dev("Crypto context initialized with X25519 key exchange (rekey thresholds: %llu packets, %s)",
159 (unsigned long long)ctx->rekey_packet_threshold, duration_str);
160 }
161 return CRYPTO_OK;
162}
163
165 if (!ctx || !password) {
166 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: ctx=%p, password=%p", ctx, password);
168 }
169
170 if (strlen(password) == 0) {
171 SET_ERRNO(ERROR_INVALID_PARAM, "Password cannot be empty");
173 }
174
175 // First initialize basic crypto
176 crypto_result_t result = crypto_init(ctx);
177 if (result != CRYPTO_OK) {
178 return result;
179 }
180
181 // Derive password key
182 result = crypto_derive_password_key(ctx, password);
183 if (result != CRYPTO_OK) {
184 crypto_destroy(ctx);
185 return result;
186 }
187
188 ctx->has_password = true;
189
190 log_dev("Crypto context initialized with password-based encryption");
191 return CRYPTO_OK;
192}
193
195 if (!ctx || !ctx->initialized) {
196 return;
197 }
198
199 // Securely wipe sensitive data
200 secure_memzero(ctx->private_key, sizeof(ctx->private_key));
201 secure_memzero(ctx->shared_key, sizeof(ctx->shared_key));
202 secure_memzero(ctx->password_key, sizeof(ctx->password_key));
203 secure_memzero(ctx->password_salt, sizeof(ctx->password_salt));
204
205 char encrypted_str[32], decrypted_str[32];
206 format_bytes_pretty(ctx->bytes_encrypted, encrypted_str, sizeof(encrypted_str));
207 format_bytes_pretty(ctx->bytes_decrypted, decrypted_str, sizeof(decrypted_str));
208
209 log_debug("Crypto context cleaned up (encrypted: %s, decrypted: %s)", encrypted_str, decrypted_str);
210
211 // Clear entire context
212 secure_memzero(ctx, sizeof(crypto_context_t));
213}
214
216 if (!ctx) {
217 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_generate_keypair: NULL context");
219 }
220
221 log_info("DEBUG: About to call crypto_box_keypair");
222 // Generate X25519 key pair for key exchange
223 if (crypto_box_keypair(ctx->public_key, ctx->private_key) != 0) {
224 SET_ERRNO(ERROR_CRYPTO, "Failed to generate X25519 key pair");
226 }
227
228 log_debug("Generated X25519 key pair for key exchange");
229 return CRYPTO_OK;
230}
231
232// =============================================================================
233// Key exchange protocol (automatic HTTPS-like key exchange)
234// =============================================================================
235
237 if (!ctx || !ctx->initialized || !public_key_out) {
239 "crypto_get_public_key: Invalid parameters (ctx=%p, initialized=%d, public_key_out=%p)", ctx,
240 ctx ? ctx->initialized : 0, public_key_out);
242 }
243
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(public_key_out, copy_size, ctx->public_key, copy_size);
247 return CRYPTO_OK;
248}
249
251 if (!ctx || !ctx->initialized || !peer_public_key) {
253 "crypto_set_peer_public_key: Invalid parameters (ctx=%p, initialized=%d, peer_public_key=%p)", ctx,
254 ctx ? ctx->initialized : 0, peer_public_key);
256 }
257
258 // Store peer's public key
259 // Bounds check to prevent buffer overflow
260 size_t copy_size = (ctx->public_key_size <= X25519_KEY_SIZE) ? ctx->public_key_size : X25519_KEY_SIZE;
261 SAFE_MEMCPY(ctx->peer_public_key, copy_size, peer_public_key, copy_size);
262 ctx->peer_key_received = true;
263
264 // Compute shared secret using X25519 and crypto_scalarmult (compatible with secretbox)
265 // This produces a raw 32-byte shared secret suitable for crypto_secretbox
266 if (crypto_scalarmult(ctx->shared_key, ctx->private_key, peer_public_key) != 0) {
267 SET_ERRNO(ERROR_CRYPTO, "Failed to compute shared secret from peer public key");
269 }
270
271 ctx->key_exchange_complete = true;
272
273 log_debug("Key exchange complete");
274 return CRYPTO_OK;
275}
276
278 if (!ctx || !ctx->initialized) {
279 return false;
280 }
281
282 // Ready if either key exchange is complete OR password is set
283 return ctx->key_exchange_complete || ctx->has_password;
284}
285
286// =============================================================================
287// Password-based encryption (optional additional layer)
288// =============================================================================
289
291 if (!password) {
292 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_validate_password: Password is NULL");
294 }
295
296 size_t password_len = strlen(password);
297
298 if (password_len < MIN_PASSWORD_LENGTH) {
299 SET_ERRNO(ERROR_INVALID_PARAM, "Password too short (minimum %d characters, got %zu)", MIN_PASSWORD_LENGTH,
300 password_len);
302 }
303
304 if (password_len > MAX_PASSWORD_LENGTH) {
305 SET_ERRNO(ERROR_INVALID_PARAM, "Password too long (maximum %d characters, got %zu)", MAX_PASSWORD_LENGTH,
306 password_len);
308 }
309
310 return CRYPTO_OK;
311}
312
314 if (!ctx || !ctx->initialized || !password) {
316 "crypto_derive_password_key: Invalid parameters (ctx=%p, initialized=%d, password=%p)", ctx,
317 ctx ? ctx->initialized : 0, password);
319 }
320
321 // Validate password length requirements
322 crypto_result_t validation_result = crypto_validate_password(password);
323 if (validation_result != CRYPTO_OK) {
324 return validation_result;
325 }
326
327 // Use deterministic salt for consistent key derivation across client/server
328 // This ensures the same password produces the same key on both sides
329 // Salt must be exactly ARGON2ID_SALT_SIZE (32) bytes
330 const char *deterministic_salt = "ascii-chat-password-salt-v1";
331 size_t salt_str_len = strlen(deterministic_salt);
332
333 // Zero-initialize the salt buffer first
334 memset(ctx->password_salt, 0, ctx->salt_size);
335
336 // Copy the salt string (will be padded with zeros to ctx->salt_size)
337 memcpy(ctx->password_salt, deterministic_salt, (salt_str_len < ctx->salt_size) ? salt_str_len : ctx->salt_size);
338
339 // Derive key using Argon2id (memory-hard, secure against GPU attacks)
340 if (crypto_pwhash(ctx->password_key, ctx->encryption_key_size, password, strlen(password), ctx->password_salt,
341 crypto_pwhash_OPSLIMIT_INTERACTIVE, // ~0.1 seconds
342 crypto_pwhash_MEMLIMIT_INTERACTIVE, // ~64MB
343 crypto_pwhash_ALG_DEFAULT) != 0) {
344 SET_ERRNO(ERROR_CRYPTO, "Password key derivation failed - possibly out of memory");
346 }
347
348 log_dev("Password key derived successfully using Argon2id with deterministic salt");
349 return CRYPTO_OK;
350}
351
352bool crypto_verify_password(const crypto_context_t *ctx, const char *password) {
353 if (!ctx || !ctx->initialized || !ctx->has_password || !password) {
354 return false;
355 }
356
357 uint8_t test_key[SECRETBOX_KEY_SIZE]; // Use maximum size for buffer
358
359 // Use the same deterministic salt for verification
360 // Salt must be exactly ARGON2ID_SALT_SIZE (32) bytes
361 const char *deterministic_salt = "ascii-chat-password-salt-v1";
362 uint8_t salt[ARGON2ID_SALT_SIZE]; // Use maximum size for buffer
363 size_t salt_str_len = strlen(deterministic_salt);
364
365 // Zero-initialize the salt buffer first
366 memset(salt, 0, ARGON2ID_SALT_SIZE);
367
368 // Copy the salt string (will be padded with zeros to ctx->salt_size)
369 memcpy(salt, deterministic_salt, (salt_str_len < ctx->salt_size) ? salt_str_len : ctx->salt_size);
370
371 // Derive key with same salt
372 if (crypto_pwhash(test_key, ctx->encryption_key_size, password, strlen(password), salt,
373 crypto_pwhash_OPSLIMIT_INTERACTIVE, crypto_pwhash_MEMLIMIT_INTERACTIVE,
374 crypto_pwhash_ALG_DEFAULT) != 0) {
375 secure_memzero(test_key, sizeof(test_key));
376 return false;
377 }
378
379 // Constant-time comparison
380 bool match = (sodium_memcmp(test_key, ctx->password_key, ctx->encryption_key_size) == 0);
381
382 secure_memzero(test_key, sizeof(test_key));
383 return match;
384}
385
386// Derive a deterministic encryption key from password for handshake
388 uint8_t encryption_key[SECRETBOX_KEY_SIZE]) {
389 if (!password || !encryption_key) {
391 "crypto_derive_password_encryption_key: Invalid parameters (password=%p, encryption_key=%p)", password,
392 encryption_key);
394 }
395
396 // Validate password length requirements
397 crypto_result_t validation_result = crypto_validate_password(password);
398 if (validation_result != CRYPTO_OK) {
399 return validation_result;
400 }
401
402 // Use deterministic salt for consistent key derivation across client/server
403 // Salt must be exactly ARGON2ID_SALT_SIZE (32) bytes
404 const char *deterministic_salt = "ascii-chat-password-salt-v1";
405 uint8_t salt[ARGON2ID_SALT_SIZE]; // Use maximum size for buffer
406 size_t salt_str_len = strlen(deterministic_salt);
407
408 // Zero-initialize the salt buffer first
409 memset(salt, 0, ARGON2ID_SALT_SIZE);
410
411 // Copy the salt string (will be padded with zeros to ARGON2ID_SALT_SIZE)
412 memcpy(salt, deterministic_salt, (salt_str_len < ARGON2ID_SALT_SIZE) ? salt_str_len : ARGON2ID_SALT_SIZE);
413
414 // Derive key using Argon2id (memory-hard, secure against GPU attacks)
415 if (crypto_pwhash(encryption_key, SECRETBOX_KEY_SIZE, password, strlen(password), salt,
416 crypto_pwhash_OPSLIMIT_INTERACTIVE, // ~0.1 seconds
417 crypto_pwhash_MEMLIMIT_INTERACTIVE, // ~64MB
418 crypto_pwhash_ALG_DEFAULT) != 0) {
419 SET_ERRNO(ERROR_CRYPTO, "Password encryption key derivation failed - possibly out of memory");
421 }
422
423 log_dev("Password encryption key derived successfully using Argon2id");
424 return CRYPTO_OK;
425}
426
427// =============================================================================
428// Encryption/Decryption operations
429// =============================================================================
430
431crypto_result_t crypto_encrypt(crypto_context_t *ctx, const uint8_t *plaintext, size_t plaintext_len,
432 uint8_t *ciphertext_out, size_t ciphertext_out_size, size_t *ciphertext_len_out) {
433 if (!ctx || !ctx->initialized || !plaintext || !ciphertext_out || !ciphertext_len_out) {
435 "Invalid parameters: ctx=%p, initialized=%d, plaintext=%p, ciphertext_out=%p, ciphertext_len_out=%p", ctx,
436 ctx ? ctx->initialized : 0, plaintext, ciphertext_out, ciphertext_len_out);
438 }
439
440 if (plaintext_len == 0 || plaintext_len > CRYPTO_MAX_PLAINTEXT_SIZE) {
441 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid plaintext length: %zu (max: %d)", plaintext_len, CRYPTO_MAX_PLAINTEXT_SIZE);
443 }
444
445 if (!crypto_is_ready(ctx)) {
446 SET_ERRNO(ERROR_CRYPTO, "Crypto context not ready for encryption");
448 }
449
450 // Check output buffer size
451 size_t required_size = plaintext_len + ctx->nonce_size + ctx->mac_size;
452 if (ciphertext_out_size < required_size) {
453 SET_ERRNO(ERROR_BUFFER, "Ciphertext buffer too small: %zu < %zu", ciphertext_out_size, required_size);
455 }
456
457 // Check for nonce counter exhaustion (extremely unlikely in practice)
458 // Starting from 1, reaching UINT64_MAX would require ~292 billion years at 60 FPS.
459 // With key rotation required at 1M packets (~16 seconds), exhaustion is virtually impossible.
460 // This check is a safety fallback that should never trigger in practice.
461 if (ctx->nonce_counter == 0 || ctx->nonce_counter == UINT64_MAX) {
462 SET_ERRNO(ERROR_CRYPTO, "Nonce counter exhausted - key rotation required");
464 }
465
466 // Choose encryption key BEFORE generating nonce (prevents nonce_counter increment on error)
467 const uint8_t *encryption_key = NULL;
468 if (ctx->key_exchange_complete) {
469 encryption_key = ctx->shared_key;
470 } else if (ctx->has_password) {
471 encryption_key = ctx->password_key;
472 } else {
473 SET_ERRNO(ERROR_CRYPTO, "No encryption key available");
475 }
476
477 // Generate nonce and place at beginning of ciphertext
478 uint8_t nonce[XSALSA20_NONCE_SIZE]; // Use maximum nonce size for buffer
479 generate_nonce(ctx, nonce);
480 SAFE_MEMCPY(ciphertext_out, ctx->nonce_size, nonce, ctx->nonce_size);
481
482 // Log nonce for debugging
483 char nonce_hex[49];
484 for (int i = 0; i < 24; i++) {
485 snprintf(&nonce_hex[i * 2], 3, "%02x", nonce[i]);
486 }
487 nonce_hex[48] = '\0';
488 log_debug("ENCRYPT_NONCE: counter=%lu nonce=%s plaintext_len=%zu", ctx->nonce_counter - 1, nonce_hex, plaintext_len);
489
490 // Encrypt using NaCl secretbox (XSalsa20 + Poly1305)
491 if (crypto_secretbox_easy(ciphertext_out + ctx->nonce_size, plaintext, plaintext_len, nonce, encryption_key) != 0) {
492 SET_ERRNO(ERROR_CRYPTO, "Encryption failed");
494 }
495
496 *ciphertext_len_out = required_size;
497 ctx->bytes_encrypted += plaintext_len;
498
499 // Increment rekey packet counter for rekeying trigger detection
500 ctx->rekey_packet_count++;
501
502 return CRYPTO_OK;
503}
504
505crypto_result_t crypto_decrypt(crypto_context_t *ctx, const uint8_t *ciphertext, size_t ciphertext_len,
506 uint8_t *plaintext_out, size_t plaintext_out_size, size_t *plaintext_len_out) {
507 if (!ctx || !ctx->initialized || !ciphertext || !plaintext_out || !plaintext_len_out) {
509 "Invalid parameters: ctx=%p, initialized=%d, ciphertext=%p, plaintext_out=%p, plaintext_len_out=%p", ctx,
510 ctx ? ctx->initialized : 0, ciphertext, plaintext_out, plaintext_len_out);
512 }
513
514 if (!crypto_is_ready(ctx)) {
515 SET_ERRNO(ERROR_CRYPTO, "Crypto context not ready for decryption");
517 }
518
519 // Check minimum ciphertext size (nonce + MAC)
520 size_t min_ciphertext_size = ctx->nonce_size + ctx->mac_size;
521 if (ciphertext_len < min_ciphertext_size) {
522 SET_ERRNO(ERROR_INVALID_PARAM, "Ciphertext too small: %zu < %zu", ciphertext_len, min_ciphertext_size);
524 }
525
526 size_t plaintext_len = ciphertext_len - ctx->nonce_size - ctx->mac_size;
527 if (plaintext_out_size < plaintext_len) {
528 SET_ERRNO(ERROR_BUFFER, "Plaintext buffer too small: %zu < %zu", plaintext_out_size, plaintext_len);
530 }
531
532 // Extract nonce from beginning of ciphertext
533 const uint8_t *nonce = ciphertext;
534 const uint8_t *encrypted_data = ciphertext + ctx->nonce_size;
535
536 // Choose decryption key (prefer shared key over password key)
537 const uint8_t *decryption_key = NULL;
538 if (ctx->key_exchange_complete) {
539 decryption_key = ctx->shared_key;
540 } else if (ctx->has_password) {
541 decryption_key = ctx->password_key;
542 } else {
543 SET_ERRNO(ERROR_CRYPTO, "No decryption key available");
545 }
546
547 // Decrypt using NaCl secretbox (XSalsa20 + Poly1305)
548 if (crypto_secretbox_open_easy(plaintext_out, encrypted_data, ciphertext_len - ctx->nonce_size, nonce,
549 decryption_key) != 0) {
550 // Log decryption failure details for debugging
551 char key_hex[65], nonce_hex[49];
552 for (int i = 0; i < 32; i++) {
553 snprintf(&key_hex[i * 2], 3, "%02x", decryption_key[i]);
554 }
555 for (int i = 0; i < 24; i++) {
556 snprintf(&nonce_hex[i * 2], 3, "%02x", nonce[i]);
557 }
558 key_hex[64] = '\0';
559 nonce_hex[48] = '\0';
560 log_error("DECRYPT_FAILED: cipherlen=%zu key=%s nonce=%s key_exchange_complete=%d has_password=%d",
561 ciphertext_len - ctx->nonce_size, key_hex, nonce_hex, ctx->key_exchange_complete, ctx->has_password);
562 SET_ERRNO(ERROR_CRYPTO, "Decryption failed - invalid MAC or corrupted data");
564 }
565
566 *plaintext_len_out = plaintext_len;
567 ctx->bytes_decrypted += plaintext_len;
568
569 return CRYPTO_OK;
570}
571
572// =============================================================================
573// Utility functions
574// =============================================================================
575
576// O(1) array lookup for crypto result strings (indexed by -result)
577static const char *const g_crypto_result_strings[] = {
578 "Success", // 0: CRYPTO_OK
579 "Initialization failed", // 1: -CRYPTO_ERROR_INIT_FAILED
580 "Invalid parameters", // 2: -CRYPTO_ERROR_INVALID_PARAMS
581 "Memory allocation failed", // 3: -CRYPTO_ERROR_MEMORY
582 "Libsodium error", // 4: -CRYPTO_ERROR_LIBSODIUM
583 "Key generation failed", // 5: -CRYPTO_ERROR_KEY_GENERATION
584 "Password derivation failed", // 6: -CRYPTO_ERROR_PASSWORD_DERIVATION
585 "Encryption failed", // 7: -CRYPTO_ERROR_ENCRYPTION
586 "Decryption failed", // 8: -CRYPTO_ERROR_DECRYPTION
587 "Invalid MAC or corrupted data", // 9: -CRYPTO_ERROR_INVALID_MAC
588 "Buffer too small", // 10: -CRYPTO_ERROR_BUFFER_TOO_SMALL
589 "Key exchange not complete", // 11: -CRYPTO_ERROR_KEY_EXCHANGE_INCOMPLETE
590 "Nonce counter exhausted", // 12: -CRYPTO_ERROR_NONCE_EXHAUSTED
591 "Rekey already in progress", // 13: -CRYPTO_ERROR_REKEY_IN_PROGRESS
592 "Rekey failed", // 14: -CRYPTO_ERROR_REKEY_FAILED
593 "Rekey rate limited", // 15: -CRYPTO_ERROR_REKEY_RATE_LIMITED
594};
595#define CRYPTO_RESULT_STRING_COUNT (sizeof(g_crypto_result_strings) / sizeof(g_crypto_result_strings[0]))
596
598 int idx = -result; // CRYPTO_OK=0, errors are negative
599 if (idx >= 0 && (size_t)idx < CRYPTO_RESULT_STRING_COUNT) {
600 return g_crypto_result_strings[idx];
601 }
602 return "Unknown error";
603}
604
605void crypto_get_status(const crypto_context_t *ctx, char *status_buffer, size_t buffer_size) {
606 if (!ctx || !status_buffer || buffer_size == 0) {
607 return;
608 }
609
610 if (!ctx->initialized) {
611 SAFE_SNPRINTF(status_buffer, buffer_size, "Not initialized");
612 return;
613 }
614
615 SAFE_SNPRINTF(status_buffer, buffer_size,
616 "Initialized: %s, Password: %s, Key Exchange: %s, Ready: %s, "
617 "Encrypted: %" PRIu64 " bytes, Decrypted: %" PRIu64 " bytes, Nonce: %" PRIu64,
618 ctx->initialized ? "yes" : "no", ctx->has_password ? "yes" : "no",
619 ctx->key_exchange_complete ? "complete" : "incomplete", crypto_is_ready(ctx) ? "yes" : "no",
621}
622
623bool crypto_secure_compare(const uint8_t *lhs, const uint8_t *rhs, size_t len) {
624 if (!lhs || !rhs) {
625 return false;
626 }
627 return sodium_memcmp(lhs, rhs, len) == 0;
628}
629
631 if (!buffer || len == 0) {
632 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_random_bytes: Invalid parameters (buffer=%p, len=%zu)", buffer, len);
634 }
635
636 crypto_result_t result = init_libsodium();
637 if (result != CRYPTO_OK) {
638 return result;
639 }
640
641 randombytes_buf(buffer, len);
642 return CRYPTO_OK;
643}
644
645// =============================================================================
646// Network integration helpers
647// =============================================================================
648
650 size_t *packet_len_out) {
651 if (!ctx || !ctx->initialized || !packet_out || !packet_len_out) {
653 "crypto_create_public_key_packet: Invalid parameters (ctx=%p, initialized=%d, packet_out=%p, "
654 "packet_len_out=%p)",
655 ctx, ctx ? ctx->initialized : 0, packet_out, packet_len_out);
657 }
658
659 size_t required_size = sizeof(uint32_t) + ctx->public_key_size; // type + key
660 if (packet_size < required_size) {
661 SET_ERRNO(ERROR_BUFFER, "crypto_create_public_key_packet: Buffer too small (size=%zu, required=%zu)", packet_size,
662 required_size);
664 }
665
666 // Pack packet: [type:4][public_key:32]
667 uint32_t packet_type = CRYPTO_PACKET_PUBLIC_KEY;
668 SAFE_MEMCPY(packet_out, sizeof(packet_type), &packet_type, sizeof(packet_type));
669 // Bounds check to prevent buffer overflow
670 size_t copy_size = (ctx->public_key_size <= X25519_KEY_SIZE) ? ctx->public_key_size : X25519_KEY_SIZE;
671 SAFE_MEMCPY(packet_out + sizeof(packet_type), copy_size, ctx->public_key, copy_size);
672
673 *packet_len_out = required_size;
674 return CRYPTO_OK;
675}
676
678 if (!ctx || !ctx->initialized || !packet) {
680 "crypto_process_public_key_packet: Invalid parameters (ctx=%p, initialized=%d, packet=%p)", ctx,
681 ctx ? ctx->initialized : 0, packet);
683 }
684
685 size_t expected_size = sizeof(uint32_t) + ctx->public_key_size;
686 if (packet_len != expected_size) {
687 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_process_public_key_packet: Invalid packet size (expected=%zu, got=%zu)",
688 expected_size, packet_len);
690 }
691
692 // Unpack packet: [type:4][public_key:32]
693 uint32_t packet_type;
694 SAFE_MEMCPY(&packet_type, sizeof(packet_type), packet, sizeof(packet_type));
695
696 if (packet_type != CRYPTO_PACKET_PUBLIC_KEY) {
697 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_process_public_key_packet: Invalid packet type (expected=%u, got=%u)",
698 CRYPTO_PACKET_PUBLIC_KEY, packet_type);
700 }
701
702 const uint8_t *peer_public_key = packet + sizeof(packet_type);
703 return crypto_set_peer_public_key(ctx, peer_public_key);
704}
705
707 uint8_t *packet_out, size_t packet_size, size_t *packet_len_out) {
708 if (!ctx || !data || !packet_out || !packet_len_out) {
710 "crypto_create_encrypted_packet: Invalid parameters (ctx=%p, data=%p, packet_out=%p, packet_len_out=%p)",
711 ctx, data, packet_out, packet_len_out);
713 }
714
715 if (!crypto_is_ready(ctx)) {
716 SET_ERRNO(ERROR_CRYPTO, "crypto_create_encrypted_packet: Crypto context not ready");
718 }
719
720 // Validate data_len to prevent integer overflow in size calculations.
721 if (data_len > CRYPTO_MAX_PLAINTEXT_SIZE) {
722 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_create_encrypted_packet: data_len %zu exceeds max %d", data_len,
725 }
726
727 // Check for integer overflow: data_len + nonce_size + mac_size
728 if (data_len > SIZE_MAX - ctx->nonce_size - ctx->mac_size) {
729 SET_ERRNO(ERROR_BUFFER, "crypto_create_encrypted_packet: encrypted_size overflow");
731 }
732 size_t encrypted_size = data_len + ctx->nonce_size + ctx->mac_size;
733
734 // Check for integer overflow: header + encrypted_size
735 size_t header_size = sizeof(uint32_t) + sizeof(uint32_t);
736 if (encrypted_size > SIZE_MAX - header_size) {
737 SET_ERRNO(ERROR_BUFFER, "crypto_create_encrypted_packet: required_size overflow");
739 }
740 size_t required_size = header_size + encrypted_size; // type + len + encrypted_data
741
742 if (packet_size < required_size) {
743 SET_ERRNO(ERROR_BUFFER, "crypto_create_encrypted_packet: Buffer too small (size=%zu, required=%zu)", packet_size,
744 required_size);
746 }
747
748 // Encrypt the data
749 size_t ciphertext_len;
750 uint8_t *encrypted_data = packet_out + sizeof(uint32_t) + sizeof(uint32_t);
751 crypto_result_t result = crypto_encrypt(ctx, data, data_len, encrypted_data,
752 packet_size - sizeof(uint32_t) - sizeof(uint32_t), &ciphertext_len);
753 if (result != CRYPTO_OK) {
754 return result;
755 }
756
757 // Pack packet: [type:4][length:4][encrypted_data:var]
758 uint32_t packet_type = CRYPTO_PACKET_ENCRYPTED_DATA;
759 uint32_t data_length = (uint32_t)ciphertext_len;
760
761 SAFE_MEMCPY(packet_out, sizeof(packet_type), &packet_type, sizeof(packet_type));
762 SAFE_MEMCPY(packet_out + sizeof(packet_type), sizeof(data_length), &data_length, sizeof(data_length));
763
764 *packet_len_out = required_size;
765 return CRYPTO_OK;
766}
767
769 uint8_t *data_out, size_t data_size, size_t *data_len_out) {
770 if (!ctx || !packet || !data_out || !data_len_out) {
772 "crypto_process_encrypted_packet: Invalid parameters (ctx=%p, packet=%p, data_out=%p, data_len_out=%p)",
773 ctx, packet, data_out, data_len_out);
775 }
776
777 if (!crypto_is_ready(ctx)) {
778 SET_ERRNO(ERROR_CRYPTO, "crypto_process_encrypted_packet: Crypto context not ready");
780 }
781
782 if (packet_len < sizeof(uint32_t) + sizeof(uint32_t)) {
783 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_process_encrypted_packet: Packet too small (size=%zu)", packet_len);
785 }
786
787 // Unpack packet: [type:4][length:4][encrypted_data:var]
788 uint32_t packet_type;
789 uint32_t data_length;
790 SAFE_MEMCPY(&packet_type, sizeof(packet_type), packet, sizeof(packet_type));
791 SAFE_MEMCPY(&data_length, sizeof(data_length), packet + sizeof(packet_type), sizeof(data_length));
792
793 if (packet_type != CRYPTO_PACKET_ENCRYPTED_DATA) {
794 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_process_encrypted_packet: Invalid packet type (expected=%u, got=%u)",
795 CRYPTO_PACKET_ENCRYPTED_DATA, packet_type);
797 }
798
799 if (packet_len != sizeof(uint32_t) + sizeof(uint32_t) + data_length) {
800 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_process_encrypted_packet: Packet length mismatch (expected=%zu, got=%zu)",
801 sizeof(uint32_t) + sizeof(uint32_t) + data_length, packet_len);
803 }
804
805 const uint8_t *encrypted_data = packet + sizeof(uint32_t) + sizeof(uint32_t);
806 return crypto_decrypt(ctx, encrypted_data, data_length, data_out, data_size, data_len_out);
807}
808
809// =============================================================================
810// Authentication and handshake functions
811// =============================================================================
812
814 if (!nonce) {
815 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_generate_nonce: NULL nonce buffer");
817 }
818
819 crypto_result_t result = init_libsodium();
820 if (result != CRYPTO_OK) {
821 return result;
822 }
823
824 randombytes_buf(nonce, 32);
825 return CRYPTO_OK;
826}
827
829 uint8_t hmac[32]) {
830 if (!ctx || !key || !data || !hmac) {
832 "crypto_compute_hmac: Invalid parameters (ctx=%p, key=%p, data=%p, hmac=%p)", ctx,
833 key, data, hmac);
834 }
835
836 crypto_result_t result = init_libsodium();
837 if (result != CRYPTO_OK) {
838 return result;
839 }
840
841 crypto_auth_hmacsha256(hmac, data, 32, key);
842 return CRYPTO_OK;
843}
844
846 size_t data_len, uint8_t hmac[32]) {
847 if (!ctx || !key || !data || !hmac || data_len == 0) {
850 "crypto_compute_hmac_ex: Invalid parameters (ctx=%p, key=%p, data=%p, data_len=%zu, hmac=%p)", ctx, key, data,
851 data_len, hmac);
852 }
853
854 crypto_result_t result = init_libsodium();
855 if (result != CRYPTO_OK) {
856 return result;
857 }
858
859 crypto_auth_hmacsha256(hmac, data, data_len, key);
860 return CRYPTO_OK;
861}
862
863bool crypto_verify_hmac(const uint8_t key[32], const uint8_t data[32], const uint8_t expected_hmac[32]) {
864 if (!key || !data || !expected_hmac) {
865 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_verify_hmac: Invalid parameters (key=%p, data=%p, expected_hmac=%p)", key,
866 data, expected_hmac);
867 return false;
868 }
869
870 uint8_t computed_hmac[32];
871 if (crypto_auth_hmacsha256(computed_hmac, data, 32, key) != 0) {
872 return false;
873 }
874
875 return sodium_memcmp(computed_hmac, expected_hmac, 32) == 0;
876}
877
878bool crypto_verify_hmac_ex(const uint8_t key[32], const uint8_t *data, size_t data_len,
879 const uint8_t expected_hmac[32]) {
880 if (!key || !data || !expected_hmac || data_len == 0) {
882 "crypto_verify_hmac_ex: Invalid parameters (key=%p, data=%p, data_len=%zu, expected_hmac=%p)", key, data,
883 data_len, expected_hmac);
884 return false;
885 }
886
887 uint8_t computed_hmac[32];
888 if (crypto_auth_hmacsha256(computed_hmac, data, data_len, key) != 0) {
889 return false;
890 }
891
892 return sodium_memcmp(computed_hmac, expected_hmac, 32) == 0;
893}
894
895// =============================================================================
896// High-level authentication helpers (shared between client and server)
897// =============================================================================
898
900 uint8_t hmac_out[32]) {
901 if (!ctx || !nonce || !hmac_out) {
902 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_compute_auth_response: Invalid parameters (ctx=%p, nonce=%p, hmac_out=%p)",
903 ctx, nonce, hmac_out);
905 }
906
907 // Ensure shared secret is derived before computing HMAC
908 // This is critical for password HMAC which binds to the shared secret
909 if (!ctx->key_exchange_complete) {
910 SET_ERRNO(ERROR_CRYPTO, "Cannot compute auth response - key exchange not complete");
912 }
913
914 // Bind password HMAC to DH shared_secret to prevent MITM
915 // Combined data: nonce || shared_secret
916 uint8_t combined_data[64];
917 memcpy(combined_data, nonce, 32);
918 memcpy(combined_data + 32, ctx->shared_key, 32);
919
920 // Use password_key if available, otherwise use shared_key
921 const uint8_t *auth_key = ctx->has_password ? ctx->password_key : ctx->shared_key;
922
923 crypto_result_t result = crypto_compute_hmac_ex(ctx, auth_key, combined_data, 64, hmac_out);
924
925 // Securely zero sensitive data containing shared secret
926 sodium_memzero(combined_data, sizeof(combined_data));
927
928 return result;
929}
930
931bool crypto_verify_auth_response(const crypto_context_t *ctx, const uint8_t nonce[32],
932 const uint8_t expected_hmac[32]) {
933 if (!ctx || !nonce || !expected_hmac) {
935 "crypto_verify_auth_response: Invalid parameters (ctx=%p, nonce=%p, expected_hmac=%p)", ctx, nonce,
936 expected_hmac);
937 return false;
938 }
939
940 // Ensure shared secret is derived before verifying HMAC
941 // This is critical for password HMAC verification which binds to the shared secret
942 if (!ctx->key_exchange_complete) {
943 SET_ERRNO(ERROR_CRYPTO, "Cannot verify auth response - key exchange not complete");
944 return false;
945 }
946
947 // Bind password HMAC to DH shared_secret to prevent MITM
948 // Combined data: nonce || shared_secret
949 uint8_t combined_data[64];
950 memcpy(combined_data, nonce, 32);
951 memcpy(combined_data + 32, ctx->shared_key, 32);
952
953 // Use password_key if available, otherwise use shared_key
954 const uint8_t *auth_key = ctx->has_password ? ctx->password_key : ctx->shared_key;
955
956 log_debug("Verifying auth response: has_password=%d, key_exchange_complete=%d, using_password_key=%d",
957 ctx->has_password, ctx->key_exchange_complete, (auth_key == ctx->password_key));
958
959 bool result = crypto_verify_hmac_ex(auth_key, combined_data, 64, expected_hmac);
960
961 // Securely zero sensitive data containing shared secret
962 sodium_memzero(combined_data, sizeof(combined_data));
963
964 return result;
965}
966
967crypto_result_t crypto_create_auth_challenge(const crypto_context_t *ctx, uint8_t *packet_out, size_t packet_size,
968 size_t *packet_len_out) {
969 if (!ctx || !ctx->initialized || !packet_out || !packet_len_out) {
970 SET_ERRNO(
972 "crypto_create_auth_challenge: Invalid parameters (ctx=%p, initialized=%d, packet_out=%p, packet_len_out=%p)",
973 ctx, ctx ? ctx->initialized : 0, packet_out, packet_len_out);
975 }
976
977 size_t required_size = sizeof(uint32_t) + ctx->auth_challenge_size; // type + nonce
978 if (packet_size < required_size) {
979 SET_ERRNO(ERROR_BUFFER, "crypto_create_auth_challenge: Buffer too small (size=%zu, required=%zu)", packet_size,
980 required_size);
982 }
983
984 // Generate random nonce (stores result in ctx->auth_nonce)
986 if (result != CRYPTO_OK) {
987 return result;
988 }
989
990 // Pack packet: [type:4][nonce:auth_challenge_size]
991 uint32_t packet_type = CRYPTO_PACKET_AUTH_CHALLENGE;
992 SAFE_MEMCPY(packet_out, sizeof(packet_type), &packet_type, sizeof(packet_type));
993 SAFE_MEMCPY(packet_out + sizeof(packet_type), ctx->auth_challenge_size, ctx->auth_nonce, ctx->auth_challenge_size);
994
995 *packet_len_out = required_size;
996 return CRYPTO_OK;
997}
998
1000 if (!ctx || !ctx->initialized || !packet) {
1002 "crypto_process_auth_challenge: Invalid parameters (ctx=%p, initialized=%d, packet=%p)", ctx,
1003 ctx ? ctx->initialized : 0, packet);
1005 }
1006
1007 size_t expected_size = sizeof(uint32_t) + ctx->auth_challenge_size; // type + nonce
1008 if (packet_len != expected_size) {
1009 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_process_auth_challenge: Invalid packet size (expected=%zu, got=%zu)",
1010 expected_size, packet_len);
1012 }
1013
1014 // Unpack packet: [type:4][nonce:auth_challenge_size]
1015 uint32_t packet_type;
1016 SAFE_MEMCPY(&packet_type, sizeof(packet_type), packet, sizeof(packet_type));
1017
1018 if (packet_type != CRYPTO_PACKET_AUTH_CHALLENGE) {
1019 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_process_auth_challenge: Invalid packet type (expected=%u, got=%u)",
1020 CRYPTO_PACKET_AUTH_CHALLENGE, packet_type);
1022 }
1023
1024 // Store the nonce for HMAC computation (use buffer size for memcpy size, actual size from context)
1025 SAFE_MEMCPY(ctx->auth_nonce, sizeof(ctx->auth_nonce), packet + sizeof(packet_type), ctx->auth_challenge_size);
1026
1027 log_debug("Auth challenge received and processed");
1028 return CRYPTO_OK;
1029}
1030
1032 if (!ctx || !ctx->initialized || !packet) {
1034 "crypto_process_auth_response: Invalid context or packet (ctx=%p, initialized=%d, packet=%p)", ctx,
1035 ctx ? ctx->initialized : 0, packet);
1037 }
1038
1039 size_t expected_size = sizeof(uint32_t) + 32; // type + hmac
1040 if (packet_len != expected_size) {
1041 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_process_auth_response: Invalid packet size (expected=%zu, got=%zu)",
1042 expected_size, packet_len);
1044 }
1045
1046 // Unpack packet: [type:4][hmac:32]
1047 uint32_t packet_type;
1048 SAFE_MEMCPY(&packet_type, sizeof(packet_type), packet, sizeof(packet_type));
1049
1050 if (packet_type != CRYPTO_PACKET_AUTH_RESPONSE) {
1051 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_process_auth_response: Invalid packet type (expected=0x%x, got=0x%x)",
1052 CRYPTO_PACKET_AUTH_RESPONSE, packet_type);
1054 }
1055
1056 const uint8_t *received_hmac = packet + sizeof(packet_type);
1057
1058 // Verify HMAC using shared secret
1059 if (!crypto_verify_hmac(ctx->shared_key, ctx->auth_nonce, received_hmac)) {
1060 SET_ERRNO(ERROR_CRYPTO, "crypto_process_auth_response: HMAC verification failed");
1062 }
1063
1064 ctx->handshake_complete = true;
1065 log_debug("Authentication successful - handshake complete");
1066 return CRYPTO_OK;
1067}
1068
1069// =============================================================================
1070// Shared Cryptographic Operations
1071// =============================================================================
1072
1074 const uint8_t *shared_secret, uint8_t *hmac_out) {
1075 if (!ctx || !password_key || !nonce || !shared_secret || !hmac_out) {
1077 "Invalid parameters: ctx=%p, password_key=%p, nonce=%p, shared_secret=%p, hmac_out=%p", ctx,
1078 password_key, nonce, shared_secret, hmac_out);
1079 }
1080
1081 // Combine nonce and shared_secret for HMAC computation
1082 // This binds the password to the DH shared secret, preventing MITM attacks
1083 uint8_t combined_data[64];
1084 memcpy(combined_data, nonce, 32);
1085 memcpy(combined_data + 32, shared_secret, 32);
1086
1087 // Compute HMAC using the password-derived key
1088 if (crypto_compute_hmac_ex(ctx, password_key, combined_data, 64, hmac_out) != 0) {
1089 // Securely zero sensitive data containing shared secret even on error
1090 sodium_memzero(combined_data, sizeof(combined_data));
1091 return SET_ERRNO(ERROR_CRYPTO, "Failed to compute password HMAC");
1092 }
1093
1094 // Securely zero sensitive data containing shared secret
1095 sodium_memzero(combined_data, sizeof(combined_data));
1096
1097 return ASCIICHAT_OK;
1098}
1099
1100asciichat_error_t crypto_verify_peer_signature(const uint8_t *peer_public_key, const uint8_t *ephemeral_key,
1101 size_t ephemeral_key_size, const uint8_t *signature) {
1102 if (!peer_public_key || !ephemeral_key || !signature) {
1103 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: peer_public_key=%p, ephemeral_key=%p, signature=%p",
1104 peer_public_key, ephemeral_key, signature);
1105 }
1106
1107 if (ephemeral_key_size == 0) {
1108 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid ephemeral key size: %zu", ephemeral_key_size);
1109 }
1110
1111 // Verify the signature using Ed25519
1112 if (crypto_sign_verify_detached(signature, ephemeral_key, ephemeral_key_size, peer_public_key) != 0) {
1113 return SET_ERRNO(ERROR_CRYPTO, "Peer signature verification failed");
1114 }
1115
1116 return ASCIICHAT_OK;
1117}
1118
1119asciichat_error_t crypto_sign_ephemeral_key(const private_key_t *private_key, const uint8_t *ephemeral_key,
1120 size_t ephemeral_key_size, uint8_t *signature_out) {
1121 if (!private_key || !ephemeral_key || !signature_out) {
1122 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: private_key=%p, ephemeral_key=%p, signature_out=%p",
1123 private_key, ephemeral_key, signature_out);
1124 }
1125
1126 if (ephemeral_key_size == 0) {
1127 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid ephemeral key size: %zu", ephemeral_key_size);
1128 return ERROR_INVALID_PARAM;
1129 }
1130
1131 // Sign the ephemeral key with our Ed25519 private key
1132 if (private_key->type == KEY_TYPE_ED25519) {
1133 if (crypto_sign_detached(signature_out, NULL, ephemeral_key, ephemeral_key_size, private_key->key.ed25519) != 0) {
1134 return SET_ERRNO(ERROR_CRYPTO, "Failed to sign ephemeral key");
1135 }
1136 } else {
1137 return SET_ERRNO(ERROR_CRYPTO, "Unsupported private key type for signing");
1138 }
1139
1140 return ASCIICHAT_OK;
1141}
1142
1143void crypto_combine_auth_data(const uint8_t *hmac, const uint8_t *challenge_nonce, uint8_t *combined_out) {
1144 if (!hmac || !challenge_nonce || !combined_out) {
1145 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: hmac=%p, challenge_nonce=%p, combined_out=%p", hmac,
1146 challenge_nonce, combined_out);
1147 return;
1148 }
1149
1150 // Combine HMAC (32 bytes) + challenge nonce (32 bytes) = 64 bytes total
1151 memcpy(combined_out, hmac, 32);
1152 memcpy(combined_out + 32, challenge_nonce, 32);
1153}
1154
1155void crypto_extract_auth_data(const uint8_t *combined_data, uint8_t *hmac_out, uint8_t *challenge_out) {
1156 if (!combined_data || !hmac_out || !challenge_out) {
1157 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: combined_data=%p, hmac_out=%p, challenge_out=%p", combined_data,
1158 hmac_out, challenge_out);
1159 return;
1160 }
1161
1162 // Extract HMAC (first 32 bytes) and challenge nonce (last 32 bytes)
1163 memcpy(hmac_out, combined_data, 32);
1164 memcpy(challenge_out, combined_data + 32, 32);
1165}
1166
1167// =============================================================================
1168// Session Rekeying Protocol Implementation
1169// =============================================================================
1170
1172 if (!ctx || !ctx->initialized) {
1173 return false;
1174 }
1175
1176 // Don't trigger rekey if one is already in progress
1177 if (ctx->rekey_in_progress) {
1178 return false;
1179 }
1180
1181 // Don't trigger rekey if handshake isn't complete yet
1182 if (!ctx->handshake_complete) {
1183 return false;
1184 }
1185
1186 // Check packet count threshold
1187 if (ctx->rekey_packet_count >= ctx->rekey_packet_threshold) {
1188 log_debug("Rekey triggered: packet count (%llu) >= threshold (%llu)", (unsigned long long)ctx->rekey_packet_count,
1189 (unsigned long long)ctx->rekey_packet_threshold);
1190 return true;
1191 }
1192
1193 // Check time threshold
1194 time_t now = time(NULL);
1195 time_t elapsed = now - ctx->rekey_last_time;
1196 if (elapsed >= ctx->rekey_time_threshold) {
1197 log_debug("Rekey triggered: time elapsed (%ld sec) >= threshold (%ld sec)", (long)elapsed,
1198 (long)ctx->rekey_time_threshold);
1199 return true;
1200 }
1201
1202 return false;
1203}
1204
1206 if (!ctx || !ctx->initialized) {
1207 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_rekey_init: Invalid context");
1209 }
1210
1211 if (ctx->rekey_in_progress) {
1212 SET_ERRNO(ERROR_CRYPTO, "Rekey already in progress");
1214 }
1215
1216 // Rate limiting: check minimum interval since last rekey
1217 time_t now = time(NULL);
1218 time_t since_last_rekey = now - ctx->rekey_last_time;
1219 time_t min_interval_seconds = (time_t)(REKEY_MIN_INTERVAL / NS_PER_SEC_INT);
1220 if (since_last_rekey < min_interval_seconds) {
1221 char elapsed_str[32], min_str[32];
1222 time_pretty((uint64_t)(since_last_rekey * 1e9), -1, elapsed_str, sizeof(elapsed_str));
1223 time_pretty((uint64_t)(min_interval_seconds * 1e9), -1, min_str, sizeof(min_str));
1224 log_warn("Rekey rate limited: %s since last rekey (minimum: %s)", elapsed_str, min_str);
1226 }
1227
1228 // Check if too many consecutive failures
1230 log_error("Too many consecutive rekey failures (%d), giving up", ctx->rekey_failure_count);
1232 }
1233
1234 // Generate new ephemeral X25519 keypair for rekeying
1235 if (crypto_box_keypair(ctx->temp_public_key, ctx->temp_private_key) != 0) {
1236 SET_ERRNO(ERROR_CRYPTO, "Failed to generate rekey ephemeral keypair");
1238 }
1239
1240 ctx->rekey_in_progress = true;
1241 ctx->has_temp_key = true;
1242
1243 log_debug("Rekey initiated (packets: %llu, time elapsed: %ld sec, attempt %d)",
1244 (unsigned long long)ctx->rekey_packet_count, (long)since_last_rekey, ctx->rekey_failure_count + 1);
1245
1246 return CRYPTO_OK;
1247}
1248
1250 if (!ctx || !ctx->initialized || !peer_new_public_key) {
1251 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_rekey_process_request: Invalid parameters");
1253 }
1254
1255 // Generate our own new ephemeral keypair (responder side)
1256 if (crypto_box_keypair(ctx->temp_public_key, ctx->temp_private_key) != 0) {
1257 SET_ERRNO(ERROR_CRYPTO, "Failed to generate rekey ephemeral keypair");
1259 }
1260
1261 // Compute new shared secret: DH(our_new_private_key, peer_new_public_key)
1262 if (crypto_scalarmult(ctx->temp_shared_key, ctx->temp_private_key, peer_new_public_key) != 0) {
1263 SET_ERRNO(ERROR_CRYPTO, "Failed to compute rekey shared secret");
1264 secure_memzero(ctx->temp_private_key, sizeof(ctx->temp_private_key));
1265 secure_memzero(ctx->temp_public_key, sizeof(ctx->temp_public_key));
1267 }
1268
1269 ctx->rekey_in_progress = true;
1270 ctx->has_temp_key = true;
1271
1272 log_debug("Rekey request processed (responder side), new shared secret computed");
1273
1274 return CRYPTO_OK;
1275}
1276
1278 if (!ctx || !ctx->initialized || !peer_new_public_key) {
1279 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_rekey_process_response: Invalid parameters");
1281 }
1282
1283 if (!ctx->rekey_in_progress || !ctx->has_temp_key) {
1284 SET_ERRNO(ERROR_CRYPTO, "No rekey in progress");
1286 }
1287
1288 // Compute new shared secret: DH(our_new_private_key, peer_new_public_key)
1289 // Use crypto_scalarmult to produce raw 32-byte shared secret for crypto_secretbox
1290 if (crypto_scalarmult(ctx->temp_shared_key, ctx->temp_private_key, peer_new_public_key) != 0) {
1291 SET_ERRNO(ERROR_CRYPTO, "Failed to compute rekey shared secret");
1292 crypto_rekey_abort(ctx);
1294 }
1295
1296 log_debug("Rekey response processed (initiator side), new shared secret computed");
1297
1298 return CRYPTO_OK;
1299}
1300
1302 if (!ctx || !ctx->initialized) {
1303 SET_ERRNO(ERROR_INVALID_PARAM, "crypto_rekey_commit: Invalid context");
1305 }
1306
1307 if (!ctx->rekey_in_progress || !ctx->has_temp_key) {
1308 SET_ERRNO(ERROR_CRYPTO, "No rekey in progress to commit");
1310 }
1311
1312 // Wipe old shared secret securely
1313 secure_memzero(ctx->shared_key, sizeof(ctx->shared_key));
1314
1315 // Switch to new shared secret
1316 SAFE_MEMCPY(ctx->shared_key, sizeof(ctx->shared_key), ctx->temp_shared_key, sizeof(ctx->temp_shared_key));
1317
1318 // Reset nonce counter to 1 (fresh start with new key)
1319 ctx->nonce_counter = 1;
1320
1321 // Generate new session ID to prevent cross-session replay
1322 randombytes_buf(ctx->session_id, sizeof(ctx->session_id));
1323
1324 // Reset rekey tracking
1325 ctx->rekey_packet_count = 0;
1326 ctx->rekey_last_time = time(NULL);
1327 ctx->rekey_count++;
1328
1329 // Clear rekey state
1330 secure_memzero(ctx->temp_public_key, sizeof(ctx->temp_public_key));
1331 secure_memzero(ctx->temp_private_key, sizeof(ctx->temp_private_key));
1332 secure_memzero(ctx->temp_shared_key, sizeof(ctx->temp_shared_key));
1333 ctx->has_temp_key = false;
1334 ctx->rekey_in_progress = false;
1335
1336 // Reset failure counter on successful rekey
1337 ctx->rekey_failure_count = 0;
1338
1339 log_debug("Rekey committed successfully (rekey #%llu, nonce reset to 1, new session_id generated)",
1340 (unsigned long long)ctx->rekey_count);
1341
1342 return CRYPTO_OK;
1343}
1344
1346 if (!ctx) {
1347 return;
1348 }
1349
1350 log_warn("Aborting rekey (attempt %d failed), keeping old encryption key", ctx->rekey_failure_count + 1);
1351
1352 // Wipe temporary keys securely
1353 secure_memzero(ctx->temp_public_key, sizeof(ctx->temp_public_key));
1354 secure_memzero(ctx->temp_private_key, sizeof(ctx->temp_private_key));
1355 secure_memzero(ctx->temp_shared_key, sizeof(ctx->temp_shared_key));
1356
1357 // Reset rekey state
1358 ctx->has_temp_key = false;
1359 ctx->rekey_in_progress = false;
1360
1361 // Increment failure counter for exponential backoff
1362 ctx->rekey_failure_count++;
1363
1364 // Continue using old key - no disruption to connection
1365}
1366
1367void crypto_get_rekey_status(const crypto_context_t *ctx, char *status_buffer, size_t buffer_size) {
1368 if (!ctx || !status_buffer || buffer_size == 0) {
1369 return;
1370 }
1371
1372 time_t now = time(NULL);
1373 time_t elapsed = now - ctx->rekey_last_time;
1374 time_t remaining_time = (ctx->rekey_time_threshold > elapsed) ? (ctx->rekey_time_threshold - elapsed) : 0;
1375 uint64_t remaining_packets = (ctx->rekey_packet_threshold > ctx->rekey_packet_count)
1377 : 0;
1378
1379 safe_snprintf(status_buffer, buffer_size,
1380 "Rekey status: %s | "
1381 "Packets: %llu/%llu (%llu remaining) | "
1382 "Time: %ld/%ld sec (%ld sec remaining) | "
1383 "Rekeys: %llu | Failures: %d",
1384 ctx->rekey_in_progress ? "IN_PROGRESS" : "IDLE", (unsigned long long)ctx->rekey_packet_count,
1385 (unsigned long long)ctx->rekey_packet_threshold, (unsigned long long)remaining_packets, (long)elapsed,
1386 (long)ctx->rekey_time_threshold, (long)remaining_time, (unsigned long long)ctx->rekey_count,
1387 ctx->rekey_failure_count);
1388}
⚠️‼️ Comprehensive thread-local error context system for ascii-chat
⚛️ Atomic operations abstraction layer with debug tracking
⚙️ Common definitions, error codes, macros, and types shared throughout the application
🔄 Network byte order conversion helpers
#define HOST_TO_NET_U64(val)
Definition endian.h:213
int buffer_size
Size of circular buffer.
Definition grep.c:90
unsigned int uint32_t
Definition common.h:58
#define SAFE_SNPRINTF(buffer, buffer_size,...)
Definition common.h:492
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:468
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.
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 in nanoseconds for testing,...
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 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 nanoseconds)
#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 in nanoseconds)
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_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.
void crypto_destroy(crypto_context_t *ctx)
Cleanup crypto context with secure memory wiping.
#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)
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_CRYPTO
Definition error_codes.h:96
@ ERROR_INVALID_PARAM
@ ERROR_BUFFER
key_type_t type
Definition key_types.h:92
uint8_t ed25519[64]
Definition key_types.h:94
union private_key_t::@14 key
@ KEY_TYPE_ED25519
Definition key_types.h:52
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#define log_dev(...)
Log a DEV message (most verbose, development only)
Definition log/log.h:534
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
#define NS_PER_SEC_INT
Definition time.h:157
int time_pretty(uint64_t nanoseconds, int decimals, char *buffer, size_t buffer_size)
Format nanoseconds as pretty duration with spaces and configurable precision.
Definition util/time.c:424
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:148
void format_bytes_pretty(size_t bytes, char *out, size_t out_capacity)
Format byte count into human-readable string.
Definition util/format.c:10
Platform initialization and static synchronization helpers.
crypto_result_t crypto_derive_password_encryption_key(const char *password, uint8_t encryption_key[SECRETBOX_KEY_SIZE])
#define CRYPTO_RESULT_STRING_COUNT
void lifecycle_init_abort(lifecycle_t *lc)
Definition lifecycle.c:99
void lifecycle_init_commit(lifecycle_t *lc)
Definition lifecycle.c:90
bool lifecycle_init_once(lifecycle_t *lc)
Definition lifecycle.c:52
Lock-free module lifecycle state machine using pure stdatomic.
#define LIFECYCLE_INIT
Static initializer for module-global lifecycle variables (no sync primitive)
Definition lifecycle.h:68
Cryptographic context structure.
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
📊 String Formatting Utilities