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