ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
keys_validation.c
Go to the documentation of this file.
1
7#include "keys_validation.h"
8#include "common.h"
9#include "asciichat_errno.h"
10#include "crypto/crypto.h" // Includes <sodium.h>
11#include <string.h>
12#include <stdlib.h>
13#include <stdio.h>
14#include <ctype.h>
15
16#ifdef _WIN32
17#include <sys/stat.h>
18#else
19#include <unistd.h>
20#include <sys/stat.h>
21#endif
22
23// =============================================================================
24// Key Validation Implementation
25// =============================================================================
26
28 if (!key) {
29 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: key=%p", key);
31 }
32
33 // Check key type
34 if (key->type == KEY_TYPE_UNKNOWN) {
35 SET_ERRNO(ERROR_CRYPTO_KEY, "Key type is unknown");
36 return ERROR_CRYPTO_KEY;
37 }
38
39 if (key->type != KEY_TYPE_ED25519 && key->type != KEY_TYPE_X25519 && key->type != KEY_TYPE_GPG) {
40 SET_ERRNO(ERROR_CRYPTO_KEY, "Unsupported key type: %d", key->type);
41 return ERROR_CRYPTO_KEY;
42 }
43
44 // Check key data is not all zeros
45 bool is_zero = true;
46 for (int i = 0; i < 32; i++) {
47 if (key->key[i] != 0) {
48 is_zero = false;
49 break;
50 }
51 }
52
53 if (is_zero) {
54 SET_ERRNO(ERROR_CRYPTO_KEY, "Key data is all zeros");
55 return ERROR_CRYPTO_KEY;
56 }
57
58 // Check comment length
59 if (strlen(key->comment) >= MAX_COMMENT_LEN) {
60 SET_ERRNO(ERROR_CRYPTO_KEY, "Key comment too long: %zu (maximum %d)", strlen(key->comment), MAX_COMMENT_LEN - 1);
61 return ERROR_CRYPTO_KEY;
62 }
63
64 return ASCIICHAT_OK;
65}
66
68 if (!key) {
69 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: key=%p", key);
71 }
72
73 // Check key type
74 if (key->type == KEY_TYPE_UNKNOWN) {
75 SET_ERRNO(ERROR_CRYPTO_KEY, "Private key type is unknown");
76 return ERROR_CRYPTO_KEY;
77 }
78
79 if (key->type != KEY_TYPE_ED25519 && key->type != KEY_TYPE_X25519) {
80 SET_ERRNO(ERROR_CRYPTO_KEY, "Unsupported private key type: %d", key->type);
81 return ERROR_CRYPTO_KEY;
82 }
83
84 // Check key data is not all zeros
85 bool is_zero = true;
86 size_t key_size = (key->type == KEY_TYPE_ED25519) ? 64 : 32;
87
88 for (size_t i = 0; i < key_size; i++) {
89 if (key->key.ed25519[i] != 0) {
90 is_zero = false;
91 break;
92 }
93 }
94
95 if (is_zero) {
96 SET_ERRNO(ERROR_CRYPTO_KEY, "Private key data is all zeros");
97 return ERROR_CRYPTO_KEY;
98 }
99
100 // Check comment length
101 if (strlen(key->key_comment) >= MAX_COMMENT_LEN) {
102 SET_ERRNO(ERROR_CRYPTO_KEY, "Private key comment too long: %zu (maximum %d)", strlen(key->key_comment),
103 MAX_COMMENT_LEN - 1);
104 return ERROR_CRYPTO_KEY;
105 }
106
107 return ASCIICHAT_OK;
108}
109
110asciichat_error_t check_key_expiry(const public_key_t *key, bool *is_expired) {
111 if (!key || !is_expired) {
112 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: key=%p, is_expired=%p", key, is_expired);
113 return ERROR_INVALID_PARAM;
114 }
115
116 // For now, we don't implement key expiry checking
117 // This would require parsing key creation dates and expiration times
118 *is_expired = false;
119
120 return ASCIICHAT_OK;
121}
122
124 if (!key_path) {
125 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: key_path=%p", key_path);
126 return ERROR_INVALID_PARAM;
127 }
128
129 // Check file permissions
130 asciichat_error_t perm_result = validate_key_permissions(key_path);
131 if (perm_result != ASCIICHAT_OK) {
132 return perm_result;
133 }
134
135 // Additional security checks have been implemented in check_key_strength()
136 // and check_key_patterns() functions. File permissions check is the critical one.
137 // Known weak keys database would require periodic updates and is beyond scope.
138
139 return ASCIICHAT_OK;
140}
141
142// =============================================================================
143// Key Format Validation
144// =============================================================================
145
147 if (!key_text) {
148 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: key_text=%p", key_text);
149 return ERROR_INVALID_PARAM;
150 }
151
152 // Check for SSH key format
153 if (strncmp(key_text, "ssh-ed25519 ", 12) != 0) {
154 SET_ERRNO(ERROR_CRYPTO_KEY, "SSH key does not start with 'ssh-ed25519 '");
155 return ERROR_CRYPTO_KEY;
156 }
157
158 // Check for base64 data
159 const char *base64_start = key_text + 12;
160 while (*base64_start == ' ' || *base64_start == '\t') {
161 base64_start++;
162 }
163
164 if (*base64_start == '\0' || *base64_start == '\n' || *base64_start == '\r') {
165 SET_ERRNO(ERROR_CRYPTO_KEY, "SSH key has no base64 data");
166 return ERROR_CRYPTO_KEY;
167 }
168
169 // Additional comprehensive SSH key validation (base64, blob structure, etc.)
170 // would require full OpenSSH format parsing. Basic format check above is sufficient
171 // to catch malformed keys. Full validation happens during key loading.
172
173 return ASCIICHAT_OK;
174}
175
177 if (!key_text) {
178 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: key_text=%p", key_text);
179 return ERROR_INVALID_PARAM;
180 }
181
182 // Check for GPG armor header
183 if (strncmp(key_text, "-----BEGIN PGP", 14) != 0) {
184 SET_ERRNO(ERROR_CRYPTO_KEY, "GPG key does not start with armor header");
185 return ERROR_CRYPTO_KEY;
186 }
187
188 // Check for GPG armor footer
189 if (strstr(key_text, "-----END PGP") == NULL) {
190 SET_ERRNO(ERROR_CRYPTO_KEY, "GPG key does not contain armor footer");
191 return ERROR_CRYPTO_KEY;
192 }
193
194 // Additional GPG key validation
195 // Validate armor format (should have valid armor headers)
196 bool has_version_header = (strstr(key_text, "Version:") != NULL);
197 bool has_charset_header = (strstr(key_text, "Charset:") != NULL || strstr(key_text, "Version:") != NULL);
198
199 if (!has_version_header && !has_charset_header) {
200 // Missing common armor headers - might be incomplete
201 log_warn("GPG armor missing version/charset headers - key might be incomplete");
202 }
203
204 // Validate base64 content (keys must have non-empty body between headers and footer)
205 const char *start_pos = strstr(key_text, "\n\n");
206 const char *end_pos = strstr(key_text, "\n-----END");
207 if (start_pos && end_pos && start_pos < end_pos) {
208 // -2 for \n\n at start_pos; check for underflow before casting to size_t
209 ptrdiff_t diff = end_pos - start_pos - 2;
210 if (diff <= 0) {
211 SET_ERRNO(ERROR_CRYPTO_KEY, "GPG key has empty or malformed body content");
212 return ERROR_CRYPTO_KEY;
213 }
214 }
215
216 return ASCIICHAT_OK;
217}
218
220 if (!key_hex) {
221 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: key_hex=%p", key_hex);
222 return ERROR_INVALID_PARAM;
223 }
224
225 // Check hex string length (32 bytes = 64 hex characters)
226 size_t hex_len = strlen(key_hex);
227 if (hex_len != 64) {
228 SET_ERRNO(ERROR_CRYPTO_KEY, "X25519 key has invalid length: %zu (expected 64)", hex_len);
229 return ERROR_CRYPTO_KEY;
230 }
231
232 // Check that all characters are valid hex
233 for (size_t i = 0; i < hex_len; i++) {
234 if (!isxdigit(key_hex[i])) {
235 SET_ERRNO(ERROR_CRYPTO_KEY, "X25519 key contains invalid hex character at position %zu: '%c'", i, key_hex[i]);
236 return ERROR_CRYPTO_KEY;
237 }
238 }
239
240 return ASCIICHAT_OK;
241}
242
243// =============================================================================
244// Key Security Checks
245// =============================================================================
246
248 if (!key || !is_weak) {
249 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: key=%p, is_weak=%p", key, is_weak);
250 return ERROR_INVALID_PARAM;
251 }
252
253 *is_weak = false;
254
255 // Check for all zeros (already checked in validate_public_key)
256 // Check for all ones
257 bool is_ones = true;
258 for (int i = 0; i < 32; i++) {
259 if (key->key[i] != 0xFF) {
260 is_ones = false;
261 break;
262 }
263 }
264
265 if (is_ones) {
266 *is_weak = true;
267 return ASCIICHAT_OK;
268 }
269
270 // Check for low entropy: Count unique bytes
271 // A properly random 32-byte key should have ~24-32 unique values
272 // Low entropy keys will have very few unique bytes
273 bool byte_seen[256] = {0};
274 int unique_bytes = 0;
275 for (int i = 0; i < 32; i++) {
276 uint8_t byte_val = key->key[i];
277 if (!byte_seen[byte_val]) {
278 byte_seen[byte_val] = true;
279 unique_bytes++;
280 }
281 }
282
283 // If less than 8 unique bytes in 32 bytes, entropy is very low
284 // This indicates non-random key generation (e.g., PRNG seed not properly initialized)
285 if (unique_bytes < 8) {
286 *is_weak = true;
287 return ASCIICHAT_OK;
288 }
289
290 // Additional weak key pattern detection:
291 // - Check for repeated patterns (handled in check_key_patterns)
292 // - Check for known weak sequences (see check_key_patterns)
293
294 return ASCIICHAT_OK;
295}
296
298 if (!key_path) {
299 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: key_path=%p", key_path);
300 return ERROR_INVALID_PARAM;
301 }
302
303#ifndef _WIN32
304 struct stat st;
305 if (stat(key_path, &st) != 0) {
306 SET_ERRNO(ERROR_CRYPTO_KEY, "Cannot stat key file: %s", key_path);
307 return ERROR_CRYPTO_KEY;
308 }
309
310 // Check for overly permissive permissions
311 if ((st.st_mode & SSH_KEY_PERMISSIONS_MASK) != 0) {
312 SET_ERRNO(ERROR_CRYPTO_KEY, "Key file has overly permissive permissions: %o (recommended: 600)", st.st_mode & 0777);
313 return ERROR_CRYPTO_KEY;
314 }
315#endif
316
317 return ASCIICHAT_OK;
318}
319
320asciichat_error_t check_key_patterns(const public_key_t *key, bool *has_weak_patterns) {
321 if (!key || !has_weak_patterns) {
322 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: key=%p, has_weak_patterns=%p", key, has_weak_patterns);
323 return ERROR_INVALID_PARAM;
324 }
325
326 *has_weak_patterns = false;
327
328 // Check for sequential patterns
329 bool is_sequential = true;
330 for (int i = 1; i < 32; i++) {
331 if (key->key[i] != key->key[i - 1] + 1) {
332 is_sequential = false;
333 break;
334 }
335 }
336
337 if (is_sequential) {
338 *has_weak_patterns = true;
339 return ASCIICHAT_OK;
340 }
341
342 // Check for repeated byte patterns (e.g., all 0xAA, all 0x55)
343 // Common weak patterns to check
344 const uint8_t weak_bytes[] = {0x00, 0xFF, 0xAA, 0x55, 0x11, 0x22, 0x33, 0x44,
345 0x66, 0x77, 0x88, 0x99, 0xBB, 0xCC, 0xDD, 0xEE};
346 for (size_t pattern_idx = 0; pattern_idx < sizeof(weak_bytes); pattern_idx++) {
347 uint8_t pattern_byte = weak_bytes[pattern_idx];
348 bool is_repeated = true;
349 for (size_t j = 0; j < 32; j++) {
350 // CORRECT: Compare against pattern_byte, not key->key[0]
351 if (key->key[j] != pattern_byte) {
352 is_repeated = false;
353 break;
354 }
355 }
356 if (is_repeated) {
357 *has_weak_patterns = true;
358 return ASCIICHAT_OK;
359 }
360 }
361
362 // Check for alternating bit patterns (e.g., 0xAA 0xBB 0xAA 0xBB or 0x55 0xAA 0x55 0xAA)
363 // A proper alternating pattern has at most 2 unique byte values that truly alternate
364 bool byte_appeared[256] = {0};
365 int unique_values = 0;
366 for (size_t i = 0; i < 32; i++) {
367 if (!byte_appeared[key->key[i]]) {
368 byte_appeared[key->key[i]] = true;
369 unique_values++;
370 }
371 }
372
373 // Only consider it weak if there are 2 or fewer unique values AND they properly alternate
374 if (unique_values <= 2) {
375 bool is_alternating = true;
376 for (size_t i = 1; i < 32; i++) {
377 // In a true alternating pattern:
378 // 1. Adjacent bytes must be different
379 // 2. Pattern must repeat every 2 bytes (byte[i] == byte[i-2])
380 if (key->key[i] == key->key[i - 1] || (i >= 2 && key->key[i] != key->key[i - 2])) {
381 is_alternating = false;
382 break;
383 }
384 }
385 if (is_alternating) {
386 *has_weak_patterns = true;
387 return ASCIICHAT_OK;
388 }
389 }
390
391 // Check for common cryptographic initialization patterns
392 // OpenSSL weak keys, old Debian SSH keys, etc.
393 uint32_t *words = (uint32_t *)key->key;
394 // Check if all 32-bit words are identical
395 bool all_words_same = true;
396 for (size_t i = 1; i < 8; i++) {
397 if (words[i] != words[0]) {
398 all_words_same = false;
399 break;
400 }
401 }
402 if (all_words_same) {
403 *has_weak_patterns = true;
404 return ASCIICHAT_OK;
405 }
406
407 return ASCIICHAT_OK;
408}
409
410// =============================================================================
411// Key Comparison and Matching
412// =============================================================================
413
414asciichat_error_t compare_public_keys(const public_key_t *key1, const public_key_t *key2, bool *are_equal) {
415 if (!key1 || !key2 || !are_equal) {
416 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: key1=%p, key2=%p, are_equal=%p", key1, key2, are_equal);
417 return ERROR_INVALID_PARAM;
418 }
419
420 *are_equal = false;
421
422 // Compare key types
423 if (key1->type != key2->type) {
424 return ASCIICHAT_OK;
425 }
426
427 // Compare key data using constant-time comparison
428 if (sodium_memcmp(key1->key, key2->key, 32) == 0) {
429 *are_equal = true;
430 }
431
432 return ASCIICHAT_OK;
433}
434
435asciichat_error_t check_key_fingerprint(const public_key_t *key, const uint8_t *fingerprint, size_t fingerprint_len,
436 bool *matches) {
437 if (!key || !fingerprint || !matches) {
438 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: key=%p, fingerprint=%p, matches=%p", key, fingerprint, matches);
439 return ERROR_INVALID_PARAM;
440 }
441
442 *matches = false;
443
444 // Generate fingerprint for the key
445 uint8_t key_fingerprint[32];
446 asciichat_error_t fingerprint_result = generate_key_fingerprint(key, key_fingerprint, 32);
447 if (fingerprint_result != ASCIICHAT_OK) {
448 return fingerprint_result;
449 }
450
451 // Compare fingerprints
452 size_t compare_len = (fingerprint_len < 32) ? fingerprint_len : 32;
453 if (sodium_memcmp(key_fingerprint, fingerprint, compare_len) == 0) {
454 *matches = true;
455 }
456
457 return ASCIICHAT_OK;
458}
459
460asciichat_error_t generate_key_fingerprint(const public_key_t *key, uint8_t *fingerprint_out, size_t fingerprint_size) {
461 if (!key || !fingerprint_out) {
462 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: key=%p, fingerprint_out=%p", key, fingerprint_out);
463 return ERROR_INVALID_PARAM;
464 }
465
466 if (fingerprint_size < 32) {
467 SET_ERRNO(ERROR_INVALID_PARAM, "Fingerprint buffer too small: %zu (minimum 32)", fingerprint_size);
468 return ERROR_INVALID_PARAM;
469 }
470
471 // Generate SHA-256 fingerprint of the key
472 if (crypto_hash_sha256(fingerprint_out, key->key, 32) != 0) {
473 SET_ERRNO(ERROR_CRYPTO, "Failed to generate key fingerprint");
474 return ERROR_CRYPTO;
475 }
476
477 return ASCIICHAT_OK;
478}
⚠️‼️ Error and/or exit() when things go bad.
unsigned int uint32_t
Definition common.h:58
unsigned char uint8_t
Definition common.h:56
#define MAX_COMMENT_LEN
#define SSH_KEY_PERMISSIONS_MASK
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:46
@ ERROR_CRYPTO_KEY
Definition error_codes.h:89
@ ASCIICHAT_OK
Definition error_codes.h:48
@ ERROR_CRYPTO
Definition error_codes.h:88
@ ERROR_INVALID_PARAM
uint8_t key[32]
Definition key_types.h:71
asciichat_error_t check_key_expiry(const public_key_t *key, bool *is_expired)
Check if a key is expired.
char comment[256]
Definition key_types.h:72
asciichat_error_t validate_gpg_key_format(const char *key_text)
Validate GPG key format and structure.
asciichat_error_t validate_ssh_key_format(const char *key_text)
Validate SSH key format.
key_type_t type
Definition key_types.h:92
char key_comment[256]
Definition key_types.h:100
asciichat_error_t validate_key_security(const char *key_path)
Validate key permissions and security.
asciichat_error_t validate_private_key(const private_key_t *key)
Validate a private key structure.
uint8_t ed25519[64]
Definition key_types.h:94
asciichat_error_t validate_key_permissions(const char *key_path)
Validate key file permissions.
asciichat_error_t validate_public_key(const public_key_t *key)
Validate a public key structure.
asciichat_error_t check_key_fingerprint(const public_key_t *key, const uint8_t *fingerprint, size_t fingerprint_len, bool *matches)
Check if key matches a fingerprint.
asciichat_error_t check_key_patterns(const public_key_t *key, bool *has_weak_patterns)
Check for key reuse or weak patterns.
asciichat_error_t check_key_strength(const public_key_t *key, bool *is_weak)
Check if key has weak parameters.
asciichat_error_t validate_x25519_key_format(const char *key_hex)
Validate X25519 key format.
union private_key_t::@1 key
asciichat_error_t compare_public_keys(const public_key_t *key1, const public_key_t *key2, bool *are_equal)
Compare two public keys for equality.
asciichat_error_t generate_key_fingerprint(const public_key_t *key, uint8_t *fingerprint_out, size_t fingerprint_size)
Generate key fingerprint.
key_type_t type
Definition key_types.h:70
@ KEY_TYPE_UNKNOWN
Definition key_types.h:51
@ KEY_TYPE_ED25519
Definition key_types.h:52
@ KEY_TYPE_GPG
Definition key_types.h:54
@ KEY_TYPE_X25519
Definition key_types.h:53
#define log_warn(...)
Log a WARN message.
Private key structure (for server –ssh-key)
Definition key_types.h:91
Public key structure.
Definition key_types.h:69