106 if (!username || !url_out) {
107 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters: username=%p, url_out=%p", username, url_out);
108 return ERROR_INVALID_PARAM;
112 SET_ERRNO(ERROR_INVALID_PARAM,
"URL buffer too small: %zu (minimum 64)", url_size);
113 return ERROR_INVALID_PARAM;
117 char clean_username[BUFFER_SIZE_SMALL];
118 SAFE_STRNCPY(clean_username, username,
sizeof(clean_username) - 1);
119 size_t len = strlen(clean_username);
120 if (len > 4 && strcmp(clean_username + len - 4,
".gpg") == 0) {
121 clean_username[len - 4] =
'\0';
125 int result =
safe_snprintf(url_out, url_size,
"https://github.com/%s.gpg", clean_username);
126 if (result < 0 || result >= (
int)url_size) {
127 SET_ERRNO(ERROR_STRING,
"Failed to construct GitHub GPG URL");
135 if (!username || !url_out) {
136 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters: username=%p, url_out=%p", username, url_out);
137 return ERROR_INVALID_PARAM;
141 SET_ERRNO(ERROR_INVALID_PARAM,
"URL buffer too small: %zu (minimum 64)", url_size);
142 return ERROR_INVALID_PARAM;
146 char clean_username[BUFFER_SIZE_SMALL];
147 SAFE_STRNCPY(clean_username, username,
sizeof(clean_username) - 1);
148 size_t len = strlen(clean_username);
149 if (len > 4 && strcmp(clean_username + len - 4,
".gpg") == 0) {
150 clean_username[len - 4] =
'\0';
154 int result =
safe_snprintf(url_out, url_size,
"https://gitlab.com/%s.gpg", clean_username);
155 if (result < 0 || result >= (
int)url_size) {
156 SET_ERRNO(ERROR_STRING,
"Failed to construct GitLab GPG URL");
168 if (!username || !keys_out || !num_keys) {
169 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters: username=%p, keys_out=%p, num_keys=%p", username, keys_out,
171 return ERROR_INVALID_PARAM;
175 char url[BUFFER_SIZE_SMALL];
177 if (url_result != ASCIICHAT_OK) {
182 char *response_text = NULL;
183 size_t response_len = 0;
184 asciichat_error_t fetch_result = https_fetch_keys(url, &response_text, &response_len);
185 if (fetch_result != ASCIICHAT_OK) {
190 asciichat_error_t parse_result =
194 SAFE_FREE(response_text);
200 if (!username || !keys_out || !num_keys) {
201 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters: username=%p, keys_out=%p, num_keys=%p", username, keys_out,
203 return ERROR_INVALID_PARAM;
207 char url[BUFFER_SIZE_SMALL];
209 if (url_result != ASCIICHAT_OK) {
214 char *response_text = NULL;
215 size_t response_len = 0;
216 asciichat_error_t fetch_result = https_fetch_keys(url, &response_text, &response_len);
217 if (fetch_result != ASCIICHAT_OK) {
222 asciichat_error_t parse_result =
226 SAFE_FREE(response_text);
232 if (!username || !keys_out || !num_keys) {
233 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters: username=%p, keys_out=%p, num_keys=%p", username, keys_out,
235 return ERROR_INVALID_PARAM;
239 char url[BUFFER_SIZE_SMALL];
241 if (url_result != ASCIICHAT_OK) {
246 char *response_text = NULL;
247 size_t response_len = 0;
248 asciichat_error_t fetch_result = https_fetch_keys(url, &response_text, &response_len);
249 if (fetch_result != ASCIICHAT_OK) {
254 asciichat_error_t parse_result =
258 SAFE_FREE(response_text);
264 if (!username || !keys_out || !num_keys) {
265 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters: username=%p, keys_out=%p, num_keys=%p", username, keys_out,
267 return ERROR_INVALID_PARAM;
271 char url[BUFFER_SIZE_SMALL];
273 if (url_result != ASCIICHAT_OK) {
278 char *response_text = NULL;
279 size_t response_len = 0;
280 asciichat_error_t fetch_result = https_fetch_keys(url, &response_text, &response_len);
281 if (fetch_result != ASCIICHAT_OK) {
286 asciichat_error_t parse_result =
290 SAFE_FREE(response_text);
300 size_t *num_keys,
size_t max_keys) {
301 if (!response_text || !keys_out || !num_keys) {
302 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters for SSH key parsing");
303 return ERROR_INVALID_PARAM;
310 size_t key_count = 0;
311 const char *line_start = response_text;
312 const char *line_end;
314 while ((line_end = strchr(line_start,
'\n')) != NULL) {
315 size_t line_len = line_end - line_start;
316 if (line_len > 0 && line_start[0] !=
'\r' && line_start[0] !=
'\n') {
319 line_start = line_end + 1;
323 if (line_start < response_text + response_len) {
327 if (key_count == 0) {
328 SET_ERRNO(ERROR_CRYPTO_KEY,
"No SSH keys found in response");
329 return ERROR_CRYPTO_KEY;
332 if (key_count > max_keys) {
333 key_count = max_keys;
337 *keys_out = SAFE_MALLOC(
sizeof(
char *) * key_count,
char **);
339 return SET_ERRNO(ERROR_MEMORY,
"Failed to allocate SSH keys array");
343 line_start = response_text;
344 size_t parsed_keys = 0;
346 while (parsed_keys < key_count && (line_end = strchr(line_start,
'\n')) != NULL) {
347 size_t line_len = line_end - line_start;
350 if (line_len > 0 && line_start[0] !=
'\r' && line_start[0] !=
'\n') {
352 (*keys_out)[parsed_keys] = SAFE_MALLOC(line_len + 1,
char *);
353 if (!(*keys_out)[parsed_keys]) {
355 for (
size_t i = 0; i < parsed_keys; i++) {
356 SAFE_FREE((*keys_out)[i]);
358 SAFE_FREE(*keys_out);
360 return SET_ERRNO(ERROR_MEMORY,
"Failed to allocate SSH key string");
364 memcpy((*keys_out)[parsed_keys], line_start, line_len);
365 (*keys_out)[parsed_keys][line_len] =
'\0';
370 line_start = line_end + 1;
374 if (parsed_keys < key_count && line_start < response_text + response_len) {
375 size_t line_len = (response_text + response_len) - line_start;
377 (*keys_out)[parsed_keys] = SAFE_MALLOC(line_len + 1,
char *);
378 if (!(*keys_out)[parsed_keys]) {
380 for (
size_t i = 0; i < parsed_keys; i++) {
381 SAFE_FREE((*keys_out)[i]);
383 SAFE_FREE(*keys_out);
385 return SET_ERRNO(ERROR_MEMORY,
"Failed to allocate SSH key string");
387 memcpy((*keys_out)[parsed_keys], line_start, line_len);
388 (*keys_out)[parsed_keys][line_len] =
'\0';
393 *num_keys = parsed_keys;
398 size_t *num_keys,
size_t max_keys) {
400 if (!response_text || !keys_out || !num_keys) {
401 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters for GPG key parsing");
402 return ERROR_INVALID_PARAM;
409 if (strncmp(response_text,
"-----BEGIN PGP", 14) != 0) {
410 SET_ERRNO(ERROR_CRYPTO_KEY,
"Response does not contain a valid GPG key");
411 return ERROR_CRYPTO_KEY;
417 if (platform_create_temp_file(temp_file,
sizeof(temp_file),
"asc_gpg_import", &fd) != 0) {
418 return SET_ERRNO(ERROR_CRYPTO_KEY,
"Failed to create temp file for GPG import");
425 platform_delete_temp_file(temp_file);
426 return SET_ERRNO(ERROR_CRYPTO_KEY,
"Failed to open temp file for writing");
430 ssize_t written = write(fd, response_text, response_len);
433 if (written != (ssize_t)response_len) {
434 platform_unlink(temp_file);
435 return SET_ERRNO(ERROR_CRYPTO_KEY,
"Failed to write GPG key to temp file");
439 char import_cmd[BUFFER_SIZE_MEDIUM];
440 safe_snprintf(import_cmd,
sizeof(import_cmd),
"gpg --import '%s' 2>&1", temp_file);
441 FILE *import_fp = NULL;
442 if (platform_popen(import_cmd,
"r", &import_fp) != ASCIICHAT_OK || !import_fp) {
443 platform_unlink(temp_file);
444 return SET_ERRNO(ERROR_CRYPTO_KEY,
"Failed to run gpg --import");
447 char import_output[2048];
448 size_t import_len = fread(import_output, 1,
sizeof(import_output) - 1, import_fp);
449 import_output[import_len] =
'\0';
450 platform_pclose(&import_fp);
451 platform_unlink(temp_file);
455 const char *key_marker =
"gpg: key ";
456 char key_ids[16][17];
457 size_t key_count = 0;
459 log_debug(
"GPG import output:\n%s", import_output);
461 char *search_pos = import_output;
462 while (key_count < 16) {
463 char *key_line = strstr(search_pos, key_marker);
467 key_line += strlen(key_marker);
469 while (i < 16 && key_line[i] !=
':' && key_line[i] !=
' ' && key_line[i] !=
'\n') {
470 key_ids[key_count][i] = key_line[i];
473 key_ids[key_count][i] =
'\0';
476 log_debug(
"Extracted GPG key ID #%zu: %s", key_count, key_ids[key_count]);
479 search_pos = key_line + i;
482 if (key_count == 0) {
483 return SET_ERRNO(ERROR_CRYPTO_KEY,
"Failed to extract any key IDs from GPG import output");
486 log_debug(
"Total GPG keys extracted from import: %zu", key_count);
489 *keys_out = SAFE_MALLOC(
sizeof(
char *) * key_count,
char **);
491 return SET_ERRNO(ERROR_MEMORY,
"Failed to allocate GPG keys array");
495 size_t valid_keys = 0;
496 for (
size_t k = 0; k < key_count; k++) {
498 char list_cmd[BUFFER_SIZE_SMALL];
500 "gpg --list-keys --with-colons --fingerprint '%s' " PLATFORM_SHELL_NULL_REDIRECT, key_ids[k]);
501 FILE *list_fp = NULL;
502 if (platform_popen(list_cmd,
"r", &list_fp) != ASCIICHAT_OK || !list_fp) {
506 char list_output[4096];
507 size_t list_len = fread(list_output, 1,
sizeof(list_output) - 1, list_fp);
508 list_output[list_len] =
'\0';
509 platform_pclose(&list_fp);
512 if (!strstr(list_output,
":22:") && !strstr(list_output,
"ed25519")) {
519 char fingerprint[41] = {0};
520 const char *fpr_marker =
"\nfpr:";
521 char *fpr_line = strstr(list_output, fpr_marker);
526 while (*fpr_line && colon_count < 9) {
527 if (*fpr_line ==
':')
534 while (fpr_len < 40 && fpr_line[fpr_len] && fpr_line[fpr_len] !=
':' && fpr_line[fpr_len] !=
'\n') {
535 fingerprint[fpr_len] = fpr_line[fpr_len];
538 fingerprint[fpr_len] =
'\0';
542 if (strlen(fingerprint) == 0) {
543 log_warn(
"Failed to extract fingerprint for key %s, using short key ID", key_ids[k]);
544 SAFE_STRNCPY(fingerprint, key_ids[k],
sizeof(fingerprint));
547 log_debug(
"Key %s -> fingerprint: %s (length: %zu)", key_ids[k], fingerprint, strlen(fingerprint));
550 size_t gpg_key_len = strlen(
"gpg:") + strlen(fingerprint) + 1;
551 (*keys_out)[valid_keys] = SAFE_MALLOC(gpg_key_len,
char *);
552 if (!(*keys_out)[valid_keys]) {
554 for (
size_t cleanup = 0; cleanup < valid_keys; cleanup++) {
555 SAFE_FREE((*keys_out)[cleanup]);
557 SAFE_FREE(*keys_out);
559 return SET_ERRNO(ERROR_MEMORY,
"Failed to allocate GPG key string");
562 safe_snprintf((*keys_out)[valid_keys], gpg_key_len,
"gpg:%s", fingerprint);
563 log_debug(
"Added valid Ed25519 key #%zu: %s", valid_keys, (*keys_out)[valid_keys]);
567 if (valid_keys == 0) {
568 SAFE_FREE(*keys_out);
570 return SET_ERRNO(ERROR_CRYPTO_KEY,
"No valid Ed25519 keys found in imported GPG keys");
573 *num_keys = valid_keys;