ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
keys.c File Reference

Go to the source code of this file.

Functions

asciichat_error_t parse_public_key (const char *input, public_key_t *key_out)
 
asciichat_error_t parse_private_key (const char *key_path, private_key_t *key_out)
 
asciichat_error_t parse_public_keys (const char *input, public_key_t *keys_out, size_t *num_keys, size_t max_keys)
 
asciichat_error_t public_key_to_x25519 (const public_key_t *key, uint8_t x25519_pk[32])
 
asciichat_error_t private_key_to_x25519 (const private_key_t *key, uint8_t x25519_sk[32])
 
asciichat_error_t fetch_github_keys (const char *username, char ***keys_out, size_t *num_keys, bool use_gpg)
 
asciichat_error_t fetch_gitlab_keys (const char *username, char ***keys_out, size_t *num_keys, bool use_gpg)
 
asciichat_error_t parse_keys_from_file (const char *path, public_key_t *keys, size_t *num_keys, size_t max_keys)
 
void format_public_key (const public_key_t *key, char *output, size_t output_size)
 
asciichat_error_t hex_decode (const char *hex, uint8_t *output, size_t output_len)
 

Function Documentation

◆ fetch_github_keys()

asciichat_error_t fetch_github_keys ( const char *  username,
char ***  keys_out,
size_t *  num_keys,
bool  use_gpg 
)

Definition at line 517 of file keys.c.

517 {
518 if (!username || !keys_out || !num_keys) {
519 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters for fetch_github_keys");
520 }
521
522 if (use_gpg) {
523 return fetch_github_gpg_keys(username, keys_out, num_keys);
524 } else {
525 return fetch_github_ssh_keys(username, keys_out, num_keys);
526 }
527}
asciichat_error_t fetch_github_ssh_keys(const char *username, char ***keys_out, size_t *num_keys)
Definition https_keys.c:167
asciichat_error_t fetch_github_gpg_keys(const char *username, char ***keys_out, size_t *num_keys)
Definition https_keys.c:231

References fetch_github_gpg_keys(), and fetch_github_ssh_keys().

◆ fetch_gitlab_keys()

asciichat_error_t fetch_gitlab_keys ( const char *  username,
char ***  keys_out,
size_t *  num_keys,
bool  use_gpg 
)

Definition at line 529 of file keys.c.

529 {
530 if (!username || !keys_out || !num_keys) {
531 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters for fetch_gitlab_keys");
532 }
533
534 if (use_gpg) {
535 return fetch_gitlab_gpg_keys(username, keys_out, num_keys);
536 } else {
537 return fetch_gitlab_ssh_keys(username, keys_out, num_keys);
538 }
539}
asciichat_error_t fetch_gitlab_gpg_keys(const char *username, char ***keys_out, size_t *num_keys)
Definition https_keys.c:263
asciichat_error_t fetch_gitlab_ssh_keys(const char *username, char ***keys_out, size_t *num_keys)
Definition https_keys.c:199

References fetch_gitlab_gpg_keys(), and fetch_gitlab_ssh_keys().

◆ format_public_key()

void format_public_key ( const public_key_t *  key,
char *  output,
size_t  output_size 
)

Definition at line 589 of file keys.c.

589 {
590 if (!key || !output || output_size == 0) {
591 return;
592 }
593
594 if (key->type == KEY_TYPE_ED25519) {
595 // Format as SSH Ed25519 public key
596 // Simple base64 encoding for 32 bytes = 43 chars + padding
597 // For now, just use hex encoding
598 char hex_key[65];
599 for (size_t i = 0; i < 32; i++) {
600 char hex_byte[3];
601 safe_snprintf(hex_byte, sizeof(hex_byte), "%02x", key->key[i]);
602 hex_key[i * 2] = hex_byte[0];
603 hex_key[i * 2 + 1] = hex_byte[1];
604 }
605 hex_key[64] = '\0';
606 safe_snprintf(output, output_size, "ssh-ed25519 %s %s", hex_key, key->comment);
607 } else if (key->type == KEY_TYPE_X25519) {
608 // Format as hex X25519 key
609 char hex_key[65];
610 for (size_t i = 0; i < 32; i++) {
611 char hex_byte[3];
612 safe_snprintf(hex_byte, sizeof(hex_byte), "%02x", key->key[i]);
613 hex_key[i * 2] = hex_byte[0];
614 hex_key[i * 2 + 1] = hex_byte[1];
615 }
616 hex_key[64] = '\0';
617 safe_snprintf(output, output_size, "x25519 %s", hex_key);
618 } else {
619 safe_snprintf(output, output_size, "unknown key type: %d", key->type);
620 }
621}
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:456

References safe_snprintf().

◆ hex_decode()

asciichat_error_t hex_decode ( const char *  hex,
uint8_t *  output,
size_t  output_len 
)

Definition at line 627 of file keys.c.

627 {
628 if (!hex || !output) {
629 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters for hex_decode");
630 }
631
632 size_t hex_len = strlen(hex);
633 size_t expected_hex_len = output_len * 2;
634
635 if (hex_len != expected_hex_len) {
636 return SET_ERRNO(ERROR_INVALID_PARAM,
637 "Hex string length (%zu) doesn't match expected output length (%zu * 2 = %zu)", hex_len,
638 output_len, expected_hex_len);
639 }
640
641 // Decode hex string to binary
642 for (size_t i = 0; i < output_len; i++) {
643 char hex_byte[3] = {hex[i * 2], hex[i * 2 + 1], '\0'};
644 char *endptr;
645 unsigned long byte = strtoul(hex_byte, &endptr, 16);
646
647 // Validate hex character
648 if (*endptr != '\0' || byte > 255) {
649 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid hex character at position %zu: '%c%c'", i * 2, hex[i * 2],
650 hex[i * 2 + 1]);
651 }
652
653 output[i] = (uint8_t)byte;
654 }
655
656 return ASCIICHAT_OK;
657}

Referenced by parse_public_key().

◆ parse_keys_from_file()

asciichat_error_t parse_keys_from_file ( const char *  path,
public_key_t *  keys,
size_t *  num_keys,
size_t  max_keys 
)

Definition at line 545 of file keys.c.

545 {
546 if (!path || !keys || !num_keys) {
547 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters for key file parsing");
548 }
549
550 *num_keys = 0;
551
552 if (!path_looks_like_path(path)) {
553 return SET_ERRNO(ERROR_CRYPTO_KEY, "Invalid keys file path: %s", path);
554 }
555
556 char *normalized_path = NULL;
557 asciichat_error_t path_result = path_validate_user_path(path, PATH_ROLE_CLIENT_KEYS, &normalized_path);
558 if (path_result != ASCIICHAT_OK) {
559 SAFE_FREE(normalized_path);
560 return path_result;
561 }
562
563 FILE *f = platform_fopen(normalized_path, "r");
564 if (!f) {
565 SAFE_FREE(normalized_path);
566 return SET_ERRNO(ERROR_CRYPTO_KEY, "Failed to open keys file: %s", path);
567 }
568
569 char line[BUFFER_SIZE_LARGE];
570 while (fgets(line, sizeof(line), f) && *num_keys < max_keys) {
571 // Remove newline
572 line[strcspn(line, "\r\n")] = 0;
573
574 // Skip empty lines and comments
575 if (strlen(line) == 0 || line[0] == '#') {
576 continue;
577 }
578
579 if (parse_public_key(line, &keys[*num_keys]) == ASCIICHAT_OK) {
580 (*num_keys)++;
581 }
582 }
583
584 (void)fclose(f);
585 SAFE_FREE(normalized_path);
586 return ASCIICHAT_OK;
587}
asciichat_error_t parse_public_key(const char *input, public_key_t *key_out)
Definition keys.c:29
bool path_looks_like_path(const char *value)
Definition path.c:766
asciichat_error_t path_validate_user_path(const char *input, path_role_t role, char **normalized_out)
Definition path.c:974
FILE * platform_fopen(const char *filename, const char *mode)

References parse_public_key(), path_looks_like_path(), path_validate_user_path(), and platform_fopen().

Referenced by parse_public_keys().

◆ parse_private_key()

asciichat_error_t parse_private_key ( const char *  key_path,
private_key_t *  key_out 
)

Definition at line 194 of file keys.c.

194 {
195 if (!key_path || !key_out) {
196 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters for private key parsing");
197 }
198
199 // Clear output structure
200 memset(key_out, 0, sizeof(private_key_t));
201
202 // Check for GPG key format: "gpg:KEYID" where KEYID is 8, 16, or 40 hex chars
203 // - 8 chars: short key ID (last 8 chars of fingerprint)
204 // - 16 chars: long key ID (last 16 chars of fingerprint)
205 // - 40 chars: full fingerprint
206 if (strncmp(key_path, "gpg:", 4) == 0) {
207 const char *key_id = key_path + 4; // Skip "gpg:" prefix
208
209 // Validate key ID format (must be 8, 16, or 40 hex characters)
210 size_t key_id_len = strlen(key_id);
211 if (key_id_len != 8 && key_id_len != 16 && key_id_len != 40) {
212 return SET_ERRNO(ERROR_CRYPTO_KEY, "Invalid GPG key ID length: %zu (expected 8, 16, or 40 hex chars)",
213 key_id_len);
214 }
215
216 // Validate hex characters
217 for (size_t i = 0; i < key_id_len; i++) {
218 char c = key_id[i];
219 if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) {
220 return SET_ERRNO(ERROR_CRYPTO_KEY, "Invalid GPG key ID: contains non-hex character '%c'", c);
221 }
222 }
223
224 // Get public key and keygrip from GPG keyring
225 uint8_t public_key[32];
226 char keygrip[64];
227 if (gpg_get_public_key(key_id, public_key, keygrip) != 0) {
228 return SET_ERRNO(ERROR_CRYPTO_KEY, "Failed to get public key from GPG for key ID: %s", key_id);
229 }
230
231 // Populate private_key_t structure for GPG agent use
232 key_out->type = KEY_TYPE_ED25519;
233 key_out->use_gpg_agent = true;
234 key_out->use_ssh_agent = false;
235
236 // Store public key (for handshake and verification)
237 memcpy(key_out->public_key, public_key, 32);
238
239 // Store keygrip (for GPG agent signing operations)
240 platform_strncpy(key_out->gpg_keygrip, sizeof(key_out->gpg_keygrip), keygrip, sizeof(key_out->gpg_keygrip) - 1);
241
242 // Store comment for display
243 safe_snprintf(key_out->key_comment, sizeof(key_out->key_comment), "GPG key %s", key_id);
244
245 // Clear the key.ed25519 field (we don't have the private key in memory)
246 // The private key stays protected in GPG agent
247 memset(key_out->key.ed25519, 0, 64);
248
249 // Set the public key in the second half of ed25519 key (standard Ed25519 format)
250 // This is for compatibility with code that expects the public key at offset 32
251 memcpy(key_out->key.ed25519 + 32, public_key, 32);
252
253 log_debug("Loaded GPG key %s (keygrip: %.40s) for agent signing", key_id, keygrip);
254 return ASCIICHAT_OK;
255 }
256
257 // Try to load file and detect format
258 char *normalized_path = NULL;
259 asciichat_error_t path_result = path_validate_user_path(key_path, PATH_ROLE_KEY_PRIVATE, &normalized_path);
260 if (path_result != ASCIICHAT_OK) {
261 SAFE_FREE(normalized_path);
262 return path_result;
263 }
264
265 // Read file content to detect format
266 FILE *f = platform_fopen(normalized_path, "r");
267 if (!f) {
268 SAFE_FREE(normalized_path);
269 return SET_ERRNO(ERROR_CRYPTO_KEY, "Failed to open private key file: %s", key_path);
270 }
271
272 // Read first 50 bytes to detect format
273 char header[50];
274 size_t bytes_read = fread(header, 1, sizeof(header) - 1, f);
275 header[bytes_read] = '\0';
276 fclose(f);
277
278 log_debug("parse_private_key: Read %zu bytes from %s, header: %.50s", bytes_read, key_path, header);
279
280 // Check for PGP armored secret key format
281 if (strstr(header, "-----BEGIN PGP PRIVATE KEY") != NULL || strstr(header, "-----BEGIN PGP SECRET KEY") != NULL) {
282 log_debug("Detected PGP armored secret key format in %s", key_path);
283 // Load entire file for OpenPGP parsing
284 f = platform_fopen(normalized_path, "r");
285 if (!f) {
286 SAFE_FREE(normalized_path);
287 return SET_ERRNO(ERROR_CRYPTO_KEY, "Failed to open GPG key file: %s", key_path);
288 }
289
290 // Get file size
291 fseek(f, 0, SEEK_END);
292 long file_size = ftell(f);
293 fseek(f, 0, SEEK_SET);
294
295 if (file_size <= 0 || file_size > 1024 * 1024) { // Sanity check: max 1MB
296 fclose(f);
297 SAFE_FREE(normalized_path);
298 return SET_ERRNO(ERROR_CRYPTO_KEY, "Invalid GPG key file size: %ld bytes", file_size);
299 }
300
301 char *file_content = SAFE_MALLOC((size_t)file_size + 1, char *);
302 size_t content_read = fread(file_content, 1, (size_t)file_size, f);
303 file_content[content_read] = '\0';
304 fclose(f);
305
306 // Parse OpenPGP armored secret key
307 uint8_t ed25519_pk[32];
308 uint8_t ed25519_sk[32];
309 asciichat_error_t openpgp_result = openpgp_parse_armored_seckey(file_content, ed25519_pk, ed25519_sk);
310 SAFE_FREE(file_content);
311 SAFE_FREE(normalized_path);
312
313 if (openpgp_result != ASCIICHAT_OK) {
314 return openpgp_result;
315 }
316
317 // Populate private_key_t structure
318 key_out->type = KEY_TYPE_ED25519;
319 key_out->use_gpg_agent = false;
320 key_out->use_ssh_agent = false;
321
322 // Store keypair in Ed25519 format (64 bytes: 32-byte seed + 32-byte public key)
323 // libsodium expects the seed in first 32 bytes and public key in second 32 bytes
324 memcpy(key_out->key.ed25519, ed25519_sk, 32); // Secret key (seed)
325 memcpy(key_out->key.ed25519 + 32, ed25519_pk, 32); // Public key
326 memcpy(key_out->public_key, ed25519_pk, 32); // Also store in public_key field
327
328 // Store comment
329 safe_snprintf(key_out->key_comment, sizeof(key_out->key_comment), "GPG Ed25519 key from %s", key_path);
330
331 log_debug("Loaded unencrypted GPG Ed25519 key from %s", key_path);
332 return ASCIICHAT_OK;
333 }
334
335 // Fall back to SSH private key parsing
336 asciichat_error_t result = parse_ssh_private_key(normalized_path, key_out);
337 SAFE_FREE(normalized_path);
338 return result;
339}
int gpg_get_public_key(const char *key_id, uint8_t *public_key_out, char *keygrip_out)
Definition export.c:251
asciichat_error_t openpgp_parse_armored_seckey(const char *armored_text, uint8_t ed25519_pk[32], uint8_t ed25519_sk[32])
Definition openpgp.c:649
asciichat_error_t parse_ssh_private_key(const char *key_path, private_key_t *key_out)
Definition ssh_keys.c:207

References gpg_get_public_key(), openpgp_parse_armored_seckey(), parse_ssh_private_key(), path_validate_user_path(), platform_fopen(), and safe_snprintf().

Referenced by client_crypto_init().

◆ parse_public_key()

asciichat_error_t parse_public_key ( const char *  input,
public_key_t *  key_out 
)

Definition at line 29 of file keys.c.

29 {
30 if (!input || !key_out) {
31 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters for public key parsing");
32 }
33
34 // Clear output structure
35 memset(key_out, 0, sizeof(public_key_t));
36
37 // Try SSH key parsing first (full format with prefix)
38 if (strncmp(input, "ssh-ed25519", 11) == 0) {
39 key_out->type = KEY_TYPE_ED25519;
40 asciichat_error_t result = parse_ssh_ed25519_line(input, key_out->key);
41 if (result == ASCIICHAT_OK) {
42 platform_strncpy(key_out->comment, sizeof(key_out->comment), "ssh-ed25519", sizeof(key_out->comment) - 1);
43 }
44 return result;
45 }
46
47 // Try raw base64 SSH public key (without "ssh-ed25519" prefix)
48 // SSH Ed25519 public keys in base64 are typically ~68 characters
49 size_t input_len = strlen(input);
50 if (input_len >= 50 && input_len <= 80) {
51 // Check if it looks like base64 (only contains valid base64 chars)
52 bool looks_like_base64 = true;
53 for (size_t i = 0; i < input_len; i++) {
54 char c = input[i];
55 if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '+' || c == '/' ||
56 c == '=')) {
57 looks_like_base64 = false;
58 break;
59 }
60 }
61
62 if (looks_like_base64) {
63 // Try to decode as SSH public key blob
64 uint8_t *blob = SAFE_MALLOC(input_len, uint8_t *);
65 size_t blob_len = 0;
66
67 const char *end = NULL;
68 int decode_result = sodium_base642bin(blob, input_len, input, input_len,
69 NULL, // ignore chars
70 &blob_len, &end, sodium_base64_VARIANT_ORIGINAL);
71
72 if (decode_result == 0 && blob_len >= 47) { // Minimum size: 4+11+4+32 = 51 bytes
73 // Validate SSH key blob structure
74 // Format: [4 bytes: length][11 bytes: "ssh-ed25519"][4 bytes: length][32 bytes: key]
75 if (blob_len >= 4) {
76 uint32_t key_type_len =
77 ((uint32_t)blob[0] << 24) | ((uint32_t)blob[1] << 16) | ((uint32_t)blob[2] << 8) | blob[3];
78 if (key_type_len == 11 && blob_len >= 15 && memcmp(blob + 4, "ssh-ed25519", 11) == 0) {
79 // Valid ssh-ed25519 blob, extract the 32-byte public key
80 if (blob_len >= 19) {
81 uint32_t pubkey_len =
82 ((uint32_t)blob[15] << 24) | ((uint32_t)blob[16] << 16) | ((uint32_t)blob[17] << 8) | blob[18];
83 if (pubkey_len == 32 && blob_len >= 51) {
84 // Extract Ed25519 public key
85 key_out->type = KEY_TYPE_ED25519;
86 memcpy(key_out->key, blob + 19, 32);
87 platform_strncpy(key_out->comment, sizeof(key_out->comment), "raw-base64",
88 sizeof(key_out->comment) - 1);
89 SAFE_FREE(blob);
90 return ASCIICHAT_OK;
91 }
92 }
93 }
94 }
95 }
96 SAFE_FREE(blob);
97 }
98 }
99
100 // Try GPG key parsing
101 if (strncmp(input, "gpg:", 4) == 0) {
102 return parse_gpg_key(input + 4, key_out);
103 }
104
105 // Try HTTPS URLs (validated via production-grade URL regex)
106 if (url_is_valid(input)) {
107 // Parse HTTPS URL to extract hostname and path
108 url_parts_t url_parts = {0};
109 asciichat_error_t parse_result = url_parse(input, &url_parts);
110 if (parse_result != ASCIICHAT_OK) {
111 return SET_ERRNO(ERROR_CRYPTO_KEY, "Failed to parse HTTPS URL: %s", input);
112 }
113
114 // Fetch the key via HTTPS
115 char *response = https_get(url_parts.host, url_parts.path);
116 url_parts_destroy(&url_parts);
117
118 if (!response) {
119 return SET_ERRNO(ERROR_CRYPTO_KEY, "Failed to fetch key from HTTPS URL: %s", input);
120 }
121
122 // Parse the fetched content as a public key
123 asciichat_error_t result = parse_public_key(response, key_out);
124 SAFE_FREE(response);
125 return result;
126 }
127
128 // Try HTTPS key fetching (GitHub/GitLab) - delegate to parse_public_keys and return first
129 if (strncmp(input, "github:", 7) == 0 || strncmp(input, "gitlab:", 7) == 0) {
130 public_key_t keys[1];
131 size_t num_keys = 0;
132 asciichat_error_t result = parse_public_keys(input, keys, &num_keys, 1);
133 if (result == ASCIICHAT_OK && num_keys > 0) {
134 *key_out = keys[0];
135 }
136 return result;
137 }
138
139 // Try raw hex key (64 hex chars = 32 bytes)
140 if (strlen(input) == 64) {
141 // Check if it's valid hex
142 bool is_valid_hex = true;
143 for (int i = 0; i < 64; i++) {
144 if (!((input[i] >= '0' && input[i] <= '9') || (input[i] >= 'a' && input[i] <= 'f') ||
145 (input[i] >= 'A' && input[i] <= 'F'))) {
146 is_valid_hex = false;
147 break;
148 }
149 }
150
151 if (is_valid_hex) {
152 // Assume it's a raw X25519 public key in hex (default for raw hex)
153 key_out->type = KEY_TYPE_X25519;
154 // Use hex_decode utility to safely decode hex string to binary
155 if (hex_decode(input, key_out->key, 32) != ASCIICHAT_OK) {
156 return SET_ERRNO(ERROR_CRYPTO_KEY, "Failed to decode hex key");
157 }
158 platform_strncpy(key_out->comment, sizeof(key_out->comment), "raw-hex", sizeof(key_out->comment) - 1);
159 return ASCIICHAT_OK;
160 }
161 }
162
163 // Try PGP armored format (-----BEGIN PGP PUBLIC KEY BLOCK-----)
164 if (strstr(input, "-----BEGIN PGP PUBLIC KEY BLOCK-----") != NULL) {
165 return parse_gpg_key_binary((const uint8_t *)input, strlen(input), key_out);
166 }
167
168 if (path_looks_like_path(input)) {
169 char *normalized_path = NULL;
170 asciichat_error_t path_result = path_validate_user_path(input, PATH_ROLE_KEY_PUBLIC, &normalized_path);
171 if (path_result != ASCIICHAT_OK) {
172 SAFE_FREE(normalized_path);
173 return path_result;
174 }
175
176 FILE *f = platform_fopen(normalized_path, "r");
177 if (f) {
178 char line[BUFFER_SIZE_LARGE];
179 if (fgets(line, sizeof(line), f)) {
180 (void)fclose(f);
181 SAFE_FREE(normalized_path);
182 // Remove newline
183 line[strcspn(line, "\r\n")] = 0;
184 return parse_public_key(line, key_out);
185 }
186 (void)fclose(f);
187 }
188 SAFE_FREE(normalized_path);
189 }
190
191 return SET_ERRNO(ERROR_CRYPTO_KEY, "Unsupported key format: %s", input);
192}
asciichat_error_t parse_gpg_key(const char *gpg_key_id, public_key_t *key_out)
Definition gpg_keys.c:25
asciichat_error_t parse_gpg_key_binary(const uint8_t *gpg_key_binary, size_t key_size, public_key_t *key_out)
Definition gpg_keys.c:71
char * https_get(const char *hostname, const char *path)
asciichat_error_t parse_public_keys(const char *input, public_key_t *keys_out, size_t *num_keys, size_t max_keys)
Definition keys.c:345
asciichat_error_t hex_decode(const char *hex, uint8_t *output, size_t output_len)
Definition keys.c:627
asciichat_error_t parse_ssh_ed25519_line(const char *line, uint8_t ed25519_pk[32])
Definition ssh_keys.c:164
void url_parts_destroy(url_parts_t *parts)
Definition url.c:281
bool url_is_valid(const char *url)
Definition url.c:81
asciichat_error_t url_parse(const char *url, url_parts_t *parts_out)
Definition url.c:166

References hex_decode(), https_get(), parse_gpg_key(), parse_gpg_key_binary(), parse_public_key(), parse_public_keys(), parse_ssh_ed25519_line(), path_looks_like_path(), path_validate_user_path(), platform_fopen(), url_is_valid(), url_parse(), and url_parts_destroy().

Referenced by check_known_host(), discovery_keys_download_https(), discovery_keys_fetch_github(), discovery_keys_fetch_gitlab(), discovery_keys_load_file(), discovery_keys_verify(), parse_keys_from_file(), parse_public_key(), and parse_public_keys().

◆ parse_public_keys()

asciichat_error_t parse_public_keys ( const char *  input,
public_key_t *  keys_out,
size_t *  num_keys,
size_t  max_keys 
)

Definition at line 345 of file keys.c.

345 {
346 if (!input || !keys_out || !num_keys || max_keys == 0) {
347 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters for multi-key parsing");
348 }
349
350 *num_keys = 0;
351
352 // Handle comma-separated key specifiers
353 if (strchr(input, ',') != NULL) {
354 size_t input_len = strlen(input);
355 char *input_copy = SAFE_MALLOC(input_len + 1, char *);
356 memcpy(input_copy, input, input_len + 1);
357
358 char *saveptr = NULL;
359 char *specifier = platform_strtok_r(input_copy, ",", &saveptr);
360
361 while (specifier != NULL && *num_keys < max_keys) {
362 // Trim leading/trailing whitespace
363 while (*specifier && isspace((unsigned char)*specifier)) {
364 specifier++;
365 }
366 char *end = specifier + strlen(specifier) - 1;
367 while (end > specifier && isspace((unsigned char)*end)) {
368 *end-- = '\0';
369 }
370
371 // Skip empty specifiers
372 if (strlen(specifier) > 0) {
373 // Recursively parse this single specifier (will not re-enter comma handling)
374 size_t current_num = 0;
375 asciichat_error_t result =
376 parse_public_keys(specifier, &keys_out[*num_keys], &current_num, max_keys - *num_keys);
377 if (result == ASCIICHAT_OK && current_num > 0) {
378 *num_keys += current_num;
379 } else {
380 log_warn("Failed to parse key specifier: %s", specifier);
381 // Continue parsing remaining specifiers even if one fails
382 }
383 }
384
385 specifier = platform_strtok_r(NULL, ",", &saveptr);
386 }
387
388 SAFE_FREE(input_copy);
389
390 if (*num_keys == 0) {
391 return SET_ERRNO(ERROR_CRYPTO_KEY, "No valid keys found in comma-separated list: %s", input);
392 }
393 return ASCIICHAT_OK;
394 }
395
396 // Check for direct SSH Ed25519 key format BEFORE checking for file paths
397 // (SSH keys can contain '/' in base64, which would match path_looks_like_path)
398 if (strncmp(input, "ssh-ed25519", 11) == 0) {
399 asciichat_error_t result = parse_public_key(input, &keys_out[0]);
400 if (result == ASCIICHAT_OK) {
401 *num_keys = 1;
402 }
403 return result;
404 }
405
406 // Check if this is a GitHub/GitLab reference - these support multiple keys
407 if (strncmp(input, "github:", 7) == 0 || strncmp(input, "gitlab:", 7) == 0) {
408 const char *username = input + 7; // Skip "github:" or "gitlab:"
409 bool is_github = (strncmp(input, "github:", 7) == 0);
410 bool is_gpg = (strstr(username, ".gpg") != NULL);
411
412 char **keys = NULL;
413 size_t num_fetched_keys = 0;
414 asciichat_error_t result;
415
416 if (is_github) {
417 if (is_gpg) {
418 result = fetch_github_gpg_keys(username, &keys, &num_fetched_keys);
419 } else {
420 result = fetch_github_ssh_keys(username, &keys, &num_fetched_keys);
421 }
422 } else {
423 if (is_gpg) {
424 result = fetch_gitlab_gpg_keys(username, &keys, &num_fetched_keys);
425 } else {
426 result = fetch_gitlab_ssh_keys(username, &keys, &num_fetched_keys);
427 }
428 }
429
430 if (result != ASCIICHAT_OK || num_fetched_keys == 0) {
431 return SET_ERRNO(ERROR_CRYPTO_KEY, "Failed to fetch keys from %s for user: %s", is_github ? "GitHub" : "GitLab",
432 username);
433 }
434
435 // Parse each fetched key (only Ed25519 keys will succeed)
436 for (size_t i = 0; i < num_fetched_keys && *num_keys < max_keys; i++) {
437 if (parse_public_key(keys[i], &keys_out[*num_keys]) == ASCIICHAT_OK) {
438 (*num_keys)++;
439 }
440 // Non-Ed25519 keys are silently skipped
441 }
442
443 // Free the keys array
444 for (size_t i = 0; i < num_fetched_keys; i++) {
445 SAFE_FREE(keys[i]);
446 }
447 SAFE_FREE(keys);
448
449 if (*num_keys == 0) {
450 return SET_ERRNO(ERROR_CRYPTO_KEY, "No valid Ed25519 keys found for %s user: %s", is_github ? "GitHub" : "GitLab",
451 username);
452 }
453
454 log_debug("Parsed %zu Ed25519 key(s) from %s user: %s", *num_keys, is_github ? "GitHub" : "GitLab", username);
455 return ASCIICHAT_OK;
456 }
457
458 // For file paths, delegate to parse_keys_from_file
459 if (path_looks_like_path(input)) {
460 return parse_keys_from_file(input, keys_out, num_keys, max_keys);
461 }
462
463 // For all other formats, use single-key parsing
464 asciichat_error_t result = parse_public_key(input, &keys_out[0]);
465 if (result == ASCIICHAT_OK) {
466 *num_keys = 1;
467 }
468 return result;
469}
asciichat_error_t parse_keys_from_file(const char *path, public_key_t *keys, size_t *num_keys, size_t max_keys)
Definition keys.c:545

References fetch_github_gpg_keys(), fetch_github_ssh_keys(), fetch_gitlab_gpg_keys(), fetch_gitlab_ssh_keys(), parse_keys_from_file(), parse_public_key(), parse_public_keys(), and path_looks_like_path().

Referenced by client_crypto_handshake(), crypto_handshake_client_key_exchange(), parse_public_key(), and parse_public_keys().

◆ private_key_to_x25519()

asciichat_error_t private_key_to_x25519 ( const private_key_t *  key,
uint8_t  x25519_sk[32] 
)

Definition at line 494 of file keys.c.

494 {
495 if (!key || !x25519_sk) {
496 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters for private_key_to_x25519");
497 }
498
499 if (key->type == KEY_TYPE_X25519) {
500 // Passthrough for X25519 keys
501 memcpy(x25519_sk, key->key.x25519, 32);
502 return ASCIICHAT_OK;
503 }
504
505 if (key->type == KEY_TYPE_ED25519) {
506 // Convert Ed25519 to X25519 (Ed25519 private key is 64 bytes: seed + public)
507 return ed25519_to_x25519_private(key->key.ed25519, x25519_sk);
508 }
509
510 return SET_ERRNO(ERROR_CRYPTO_KEY, "Unsupported key type for X25519 conversion: %d", key->type);
511}
asciichat_error_t ed25519_to_x25519_private(const uint8_t ed25519_sk[64], uint8_t x25519_sk[32])
Definition ssh_keys.c:1014

References ed25519_to_x25519_private().

◆ public_key_to_x25519()

asciichat_error_t public_key_to_x25519 ( const public_key_t *  key,
uint8_t  x25519_pk[32] 
)

Definition at line 475 of file keys.c.

475 {
476 if (!key || !x25519_pk) {
477 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters for public_key_to_x25519");
478 }
479
480 if (key->type == KEY_TYPE_X25519) {
481 // Passthrough for X25519 keys
482 memcpy(x25519_pk, key->key, 32);
483 return ASCIICHAT_OK;
484 }
485
486 if (key->type == KEY_TYPE_ED25519) {
487 // Convert Ed25519 to X25519
488 return ed25519_to_x25519_public(key->key, x25519_pk);
489 }
490
491 return SET_ERRNO(ERROR_CRYPTO_KEY, "Unsupported key type for X25519 conversion: %d", key->type);
492}
asciichat_error_t ed25519_to_x25519_public(const uint8_t ed25519_pk[32], uint8_t x25519_pk[32])
Definition ssh_keys.c:1001

References ed25519_to_x25519_public().