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

GPG public key export implementation. More...

Go to the source code of this file.

Functions

int gpg_get_public_key (const char *key_id, uint8_t *public_key_out, char *keygrip_out)
 

Detailed Description

GPG public key export implementation.

Definition in file export.c.

Function Documentation

◆ gpg_get_public_key()

int gpg_get_public_key ( const char *  key_id,
uint8_t *  public_key_out,
char *  keygrip_out 
)

Definition at line 251 of file export.c.

251 {
252 if (!key_id || !public_key_out) {
253 log_error("Invalid arguments to gpg_get_public_key");
254 return -1;
255 }
256
257 // SECURITY: Validate key_id to prevent command injection
258 // GPG key IDs should be hexadecimal (0-9, a-f, A-F)
259 if (!validate_shell_safe(key_id, NULL)) {
260 log_error("Invalid GPG key ID format - contains unsafe characters: %s", key_id);
261 return -1;
262 }
263
264 // Additional validation: ensure key_id is hex alphanumeric
265 for (size_t i = 0; key_id[i] != '\0'; i++) {
266 if (!isxdigit((unsigned char)key_id[i])) {
267 log_error("Invalid GPG key ID format - must be hexadecimal: %s", key_id);
268 return -1;
269 }
270 }
271
272 // Escape key_id for safe use in shell command (single quotes)
273 char escaped_key_id[BUFFER_SIZE_MEDIUM];
274 if (!escape_shell_single_quotes(key_id, escaped_key_id, sizeof(escaped_key_id))) {
275 log_error("Failed to escape GPG key ID for shell command");
276 return -1;
277 }
278
279 // Use gpg to list the key and get the keygrip
280 char cmd[BUFFER_SIZE_LARGE];
281 safe_snprintf(cmd, sizeof(cmd), "gpg --list-keys --with-keygrip --with-colons 0x%s " PLATFORM_SHELL_NULL_REDIRECT,
282 escaped_key_id);
283
284 FILE *fp = NULL;
285 if (platform_popen(cmd, "r", &fp) != ASCIICHAT_OK || !fp) {
286 log_error("Failed to run gpg command - GPG may not be installed");
287#ifdef _WIN32
288 log_error("To install GPG on Windows, download Gpg4win from:");
289 log_error(" https://www.gpg4win.org/download.html");
290#elif defined(__APPLE__)
291 log_error("To install GPG on macOS, use Homebrew:");
292 log_error(" brew install gnupg");
293#else
294 log_error("To install GPG on Linux:");
295 log_error(" Debian/Ubuntu: sudo apt-get install gnupg");
296 log_error(" Fedora/RHEL: sudo dnf install gnupg2");
297 log_error(" Arch Linux: sudo pacman -S gnupg");
298 log_error(" Alpine Linux: sudo apk add gnupg");
299#endif
300 return -1;
301 }
302
303 char line[BUFFER_SIZE_XLARGE];
304 char found_keygrip[128] = {0};
305 bool found_key = false;
306
307 // Parse gpg output
308 // Format: pub:..., grp:::::::::<keygrip>:
309 while (fgets(line, sizeof(line), fp)) {
310 if (strncmp(line, "pub:", 4) == 0) {
311 // Found the public key line
312 found_key = true;
313 } else if (found_key && strncmp(line, "grp:", 4) == 0) {
314 // Extract keygrip using PCRE2 regex
315 // Format: grp:::::::::D52FF935FBA59609EE65E1685287828242A1EA1A:
316 char *keygrip_extracted = NULL;
317
318 if (crypto_regex_extract_gpg_keygrip(line, &keygrip_extracted)) {
319 // Successfully extracted keygrip
320 SAFE_STRNCPY(found_keygrip, keygrip_extracted, sizeof(found_keygrip));
321 if (keygrip_out) {
322 SAFE_STRNCPY(keygrip_out, found_keygrip, 41);
323 }
324 SAFE_FREE(keygrip_extracted);
325 } else {
326 // Fallback to manual parsing if regex fails
327 const char *grp_start = line + 4;
328 int colon_count = 0;
329 while (*grp_start && colon_count < 8) {
330 if (*grp_start == ':') {
331 colon_count++;
332 }
333 grp_start++;
334 }
335
336 if (colon_count == 8) {
337 const char *grp_end = strchr(grp_start, ':');
338 if (grp_end) {
339 size_t grp_len = grp_end - grp_start;
340 if (grp_len < sizeof(found_keygrip)) {
341 memcpy(found_keygrip, grp_start, grp_len);
342 found_keygrip[grp_len] = '\0';
343
344 if (keygrip_out) {
345 SAFE_STRNCPY(keygrip_out, found_keygrip, 41);
346 }
347 }
348 }
349 }
350 }
351 break;
352 }
353 }
354
355 platform_pclose(&fp);
356
357 if (!found_key || strlen(found_keygrip) == 0) {
358 log_error("Could not find GPG key with ID: %s", key_id);
359 return -1;
360 }
361
362 log_debug("Found keygrip for key %s: %s", key_id, found_keygrip);
363
364 // Try to use GPG agent API to read the public key directly via READKEY command
365 int agent_sock = gpg_agent_connect();
366 if (agent_sock < 0) {
367 log_debug("GPG agent not available, falling back to gpg --export for public key extraction");
368 // Fallback: Use gpg --export to get the public key
369 int export_result = gpg_export_public_key(key_id, public_key_out);
370 if (export_result == 0) {
371 log_debug("Successfully extracted public key using fallback method");
372 } else {
373 log_error("Fallback public key extraction failed for key ID: %s", key_id);
374 }
375 return export_result;
376 }
377
378 // Send READKEY command with keygrip to get the public key S-expression
379 char readkey_cmd[BUFFER_SIZE_SMALL];
380 safe_snprintf(readkey_cmd, sizeof(readkey_cmd), "READKEY %s\n", found_keygrip);
381
382 // Cast agent_sock back to pipe_t (gpg_agent_connect returns pipe_t cast to int for portability)
383 pipe_t agent_pipe = (pipe_t)(intptr_t)agent_sock;
384 ssize_t bytes_written = platform_pipe_write(agent_pipe, (const unsigned char *)readkey_cmd, strlen(readkey_cmd));
385 if (bytes_written != (ssize_t)strlen(readkey_cmd)) {
386 log_error("Failed to send READKEY command to GPG agent");
387 gpg_agent_disconnect(agent_sock);
388 return -1;
389 }
390
391 // Read the response (public key S-expression)
392 char response[BUFFER_SIZE_XXXLARGE];
393 memset(response, 0, sizeof(response));
394 ssize_t bytes_read = platform_pipe_read(agent_pipe, (unsigned char *)response, sizeof(response) - 1);
395
396 gpg_agent_disconnect(agent_sock);
397
398 if (bytes_read <= 0) {
399 log_error("Failed to read READKEY response from GPG agent");
400 return -1;
401 }
402
403 // Parse the S-expression to extract Ed25519 public key (q value)
404 // GPG agent returns binary S-expressions in format: (1:q<length>:<binary-data>)
405 // Example: (1:q33:<33-bytes>) where first byte is 0x40 (Ed25519 prefix), then 32-byte key
406 const char *q_marker = strstr(response, "(1:q");
407 if (!q_marker) {
408 log_warn("Failed to find public key (1:q) in GPG agent READKEY response, trying gpg --export fallback");
409 log_debug("Response was: %.*s", (int)(bytes_read < 200 ? bytes_read : 200), response);
410 gpg_agent_disconnect(agent_sock);
411
412 // Fallback: Use gpg --export for public-only keys
413 int export_result = gpg_export_public_key(key_id, public_key_out);
414 if (export_result == 0) {
415 log_debug("Successfully extracted public key using gpg --export fallback");
416 } else {
417 log_error("Fallback public key extraction failed for key ID: %s", key_id);
418 }
419 return export_result;
420 }
421
422 // Skip "(1:q" to get to the length field
423 const char *len_start = q_marker + 4;
424
425 // Parse the length (e.g., "33:")
426 char *colon = strchr(len_start, ':');
427 if (!colon) {
428 log_error("Malformed S-expression: missing colon after length");
429 return -1;
430 }
431
432 size_t key_len = strtoul(len_start, NULL, 10);
433 if (key_len != 33) {
434 log_error("Unexpected Ed25519 public key length: %zu bytes (expected 33)", key_len);
435 return -1;
436 }
437
438 // Skip the colon to get to the binary data
439 const unsigned char *binary_start = (const unsigned char *)(colon + 1);
440
441 // Ed25519 public keys in GPG format have a 0x40 prefix byte, then 32 bytes of actual key
442 if (binary_start[0] != 0x40) {
443 log_error("Invalid Ed25519 public key prefix: 0x%02x (expected 0x40)", binary_start[0]);
444 return -1;
445 }
446
447 // Copy the 32-byte public key (skip the 0x40 prefix)
448 memcpy(public_key_out, binary_start + 1, 32);
449
450 log_debug("Extracted Ed25519 public key from GPG agent via READKEY command");
451 return 0;
452}
int gpg_agent_connect(void)
Definition agent.c:96
void gpg_agent_disconnect(int handle_as_int)
Definition agent.c:146
_Atomic uint64_t bytes_written
Definition mmap.c:42
bool crypto_regex_extract_gpg_keygrip(const char *line, char **keygrip_out)
Definition regex.c:267
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:456
bool escape_shell_single_quotes(const char *str, char *out_buffer, size_t out_buffer_size)
bool validate_shell_safe(const char *str, const char *allowed_chars)
Definition util/string.c:59

References bytes_written, crypto_regex_extract_gpg_keygrip(), escape_shell_single_quotes(), gpg_agent_connect(), gpg_agent_disconnect(), safe_snprintf(), and validate_shell_safe().

Referenced by extract_ed25519_from_gpg(), and parse_private_key().