C · 24820 bytes Raw Blame History
1 /* GPG key and environment management with comprehensive isolation and security
2 * Implements per-account GNUPGHOME environments to prevent GPG key mixing
3 */
4
5 #define _POSIX_C_SOURCE 200809L
6 #define _DEFAULT_SOURCE
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <unistd.h>
11 #include <sys/wait.h>
12 #include <sys/stat.h>
13 #include <signal.h>
14 #include <errno.h>
15 #include <fcntl.h>
16
17 #include "gpg_manager.h"
18 #include "error.h"
19 #include "utils.h"
20 #include "display.h"
21 #include "git_ops.h"
22
23 /* Internal helper functions */
24 static int create_isolated_gnupg_home_dir(const char *gnupg_home);
25 static int execute_gpg_command_in_env(const gpg_config_t *gpg_config,
26 const char *command, char *output, size_t output_size);
27 static int copy_key_from_system_keyring(const gpg_config_t *gpg_config, const char *key_id);
28 static int validate_gnupg_home_permissions(const char *gnupg_home);
29 static int setup_gpg_agent_config(const char *gnupg_home);
30
31 /* Initialize GPG manager with specified mode */
32 int gpg_manager_init(gpg_config_t *gpg_config, gpg_mode_t mode) {
33 if (!gpg_config) {
34 set_error(ERR_INVALID_ARGS, "Invalid arguments to gpg_manager_init");
35 return -1;
36 }
37
38 log_debug("Initializing GPG manager with mode: %d", mode);
39
40 /* Initialize GPG configuration */
41 memset(gpg_config, 0, sizeof(gpg_config_t));
42 gpg_config->mode = mode;
43 gpg_config->signing_enabled = false;
44 gpg_config->home_owned = false;
45
46 /* Initialize based on mode */
47 switch (mode) {
48 case GPG_MODE_SYSTEM:
49 /* Use system GNUPGHOME */
50 log_debug("Using system GPG environment");
51 break;
52
53 case GPG_MODE_ISOLATED:
54 /* Will create isolated GNUPGHOME per account */
55 log_debug("GPG manager initialized for isolated environments");
56 break;
57
58 case GPG_MODE_SHARED:
59 /* Shared GNUPGHOME with key switching */
60 log_debug("GPG manager initialized for shared environment");
61 break;
62
63 default:
64 set_error(ERR_INVALID_ARGS, "Invalid GPG mode: %d", mode);
65 return -1;
66 }
67
68 /* Verify GPG is available */
69 if (!command_exists("gpg")) {
70 set_error(ERR_GPG_NOT_FOUND, "GPG command not found in PATH");
71 return -1;
72 }
73
74 log_info("GPG manager initialized successfully");
75 return 0;
76 }
77
78 /* Cleanup GPG manager */
79 void gpg_manager_cleanup(gpg_config_t *gpg_config) {
80 if (!gpg_config) {
81 return;
82 }
83
84 log_debug("Cleaning up GPG manager");
85
86 /* Clean up owned GNUPGHOME directory if needed */
87 if (gpg_config->home_owned && strlen(gpg_config->gnupg_home) > 0) {
88 log_debug("Cleaning up owned GNUPGHOME: %s", gpg_config->gnupg_home);
89
90 /* Only remove if it's clearly our isolated directory */
91 if (strstr(gpg_config->gnupg_home, "gitswitch-gpg") != NULL) {
92 char command[512];
93 if (safe_snprintf(command, sizeof(command), "rm -rf '%s'", gpg_config->gnupg_home) == 0) {
94 if (system(command) != 0) {
95 log_warning("Failed to remove GNUPGHOME directory: %s", gpg_config->gnupg_home);
96 } else {
97 log_debug("Successfully removed GNUPGHOME: %s", gpg_config->gnupg_home);
98 }
99 }
100 }
101 }
102
103 /* Clear configuration */
104 memset(gpg_config, 0, sizeof(gpg_config_t));
105
106 log_debug("GPG manager cleanup completed");
107 }
108
109 /* Switch to account's GPG configuration with complete isolation */
110 int gpg_switch_account(gpg_config_t *gpg_config, const account_t *account) {
111 if (!gpg_config || !account) {
112 set_error(ERR_INVALID_ARGS, "Invalid arguments to gpg_switch_account");
113 return -1;
114 }
115
116 /* Skip if GPG not enabled for account */
117 if (!account->gpg_enabled || strlen(account->gpg_key_id) == 0) {
118 log_debug("GPG not enabled for account: %s", account->name);
119 return 0;
120 }
121
122 log_info("Switching to GPG configuration for account: %s", account->name);
123 log_debug("Account GPG key ID: %s", account->gpg_key_id);
124
125 /* Handle different GPG modes */
126 switch (gpg_config->mode) {
127 case GPG_MODE_SYSTEM:
128 /* Just validate key exists in system keyring */
129 if (gpg_validate_key(gpg_config, account->gpg_key_id) != 0) {
130 set_error(ERR_GPG_KEY_NOT_FOUND, "GPG key not found in system keyring: %s",
131 account->gpg_key_id);
132 return -1;
133 }
134 break;
135
136 case GPG_MODE_ISOLATED:
137 /* Create isolated GNUPGHOME for account */
138 if (gpg_create_isolated_home(gpg_config, account) != 0) {
139 set_error(ERR_GPG_KEY_FAILED, "Failed to create isolated GPG environment: %s",
140 get_last_error()->message);
141 return -1;
142 }
143
144 /* Copy key from system keyring to isolated environment */
145 if (copy_key_from_system_keyring(gpg_config, account->gpg_key_id) != 0) {
146 log_warning("Failed to copy GPG key to isolated environment: %s",
147 get_last_error()->message);
148 /* Continue anyway - maybe key is already there */
149 }
150
151 /* Validate key is available in isolated environment */
152 if (gpg_validate_key(gpg_config, account->gpg_key_id) != 0) {
153 set_error(ERR_GPG_KEY_NOT_FOUND, "GPG key not available in isolated environment: %s",
154 account->gpg_key_id);
155 return -1;
156 }
157 break;
158
159 case GPG_MODE_SHARED:
160 /* Validate key exists and switch to it */
161 if (gpg_validate_key(gpg_config, account->gpg_key_id) != 0) {
162 set_error(ERR_GPG_KEY_NOT_FOUND, "GPG key not found: %s", account->gpg_key_id);
163 return -1;
164 }
165 break;
166
167 default:
168 set_error(ERR_INVALID_ARGS, "Invalid GPG mode: %d", gpg_config->mode);
169 return -1;
170 }
171
172 /* Update GPG configuration */
173 safe_strncpy(gpg_config->current_key_id, account->gpg_key_id, sizeof(gpg_config->current_key_id));
174 gpg_config->signing_enabled = account->gpg_signing_enabled;
175
176 /* Set environment variable if using isolated mode */
177 if (gpg_config->mode == GPG_MODE_ISOLATED) {
178 if (gpg_set_environment(gpg_config) != 0) {
179 log_warning("Failed to set GPG environment variable: %s", get_last_error()->message);
180 }
181 }
182
183 /* Test GPG signing if enabled */
184 if (account->gpg_signing_enabled) {
185 if (gpg_test_signing(gpg_config, account->gpg_key_id) != 0) {
186 log_warning("GPG signing test failed for key: %s", account->gpg_key_id);
187 /* Don't fail completely, just warn */
188 } else {
189 log_info("GPG signing test passed for key: %s", account->gpg_key_id);
190 }
191 }
192
193 log_info("Successfully switched to GPG configuration for account: %s", account->name);
194 return 0;
195 }
196
197 /* Create isolated GNUPGHOME for account */
198 int gpg_create_isolated_home(gpg_config_t *gpg_config, const account_t *account) {
199 char gnupg_base_dir[MAX_PATH_LEN];
200 char gnupg_home[MAX_PATH_LEN];
201 const char *home_dir;
202 const char *runtime_dir;
203
204 if (!gpg_config || !account) {
205 set_error(ERR_INVALID_ARGS, "Invalid arguments to gpg_create_isolated_home");
206 return -1;
207 }
208
209 /* Determine base directory for isolated GNUPGHOME */
210 runtime_dir = getenv("XDG_RUNTIME_DIR");
211 home_dir = getenv("HOME");
212
213 if (runtime_dir) {
214 /* Use XDG runtime directory if available */
215 if (safe_snprintf(gnupg_base_dir, sizeof(gnupg_base_dir), "%s/gitswitch-gpg", runtime_dir) != 0) {
216 set_error(ERR_INVALID_PATH, "GNUPG base directory path too long");
217 return -1;
218 }
219 } else if (home_dir) {
220 /* Fall back to home directory */
221 if (safe_snprintf(gnupg_base_dir, sizeof(gnupg_base_dir), "%s/.local/run/gitswitch-gpg", home_dir) != 0) {
222 set_error(ERR_INVALID_PATH, "GNUPG base directory path too long");
223 return -1;
224 }
225 } else {
226 /* Last resort: use /tmp */
227 if (safe_snprintf(gnupg_base_dir, sizeof(gnupg_base_dir), "/tmp/gitswitch-gpg-%d", getuid()) != 0) {
228 set_error(ERR_INVALID_PATH, "GNUPG base directory path too long");
229 return -1;
230 }
231 }
232
233 /* Create base directory */
234 if (create_directory_recursive(gnupg_base_dir, 0700) != 0) {
235 set_error(ERR_FILE_IO, "Failed to create GPG base directory: %s", gnupg_base_dir);
236 return -1;
237 }
238
239 /* Create account-specific GNUPGHOME */
240 if (safe_snprintf(gnupg_home, sizeof(gnupg_home), "%s/%s", gnupg_base_dir, account->name) != 0) {
241 set_error(ERR_INVALID_PATH, "GNUPGHOME path too long");
242 return -1;
243 }
244
245 /* Create isolated GNUPGHOME directory */
246 if (create_isolated_gnupg_home_dir(gnupg_home) != 0) {
247 return -1;
248 }
249
250 /* Set up GPG agent configuration */
251 if (setup_gpg_agent_config(gnupg_home) != 0) {
252 log_warning("Failed to set up GPG agent config: %s", get_last_error()->message);
253 /* Continue anyway */
254 }
255
256 /* Update GPG configuration */
257 safe_strncpy(gpg_config->gnupg_home, gnupg_home, sizeof(gpg_config->gnupg_home));
258 gpg_config->home_owned = true;
259
260 log_info("Created isolated GNUPGHOME: %s", gnupg_home);
261 return 0;
262 }
263
264 /* Import GPG key from file or keyserver */
265 int gpg_import_key(gpg_config_t *gpg_config, const char *key_source) {
266 char command[512];
267 char output[1024];
268 int result;
269
270 if (!gpg_config || !key_source) {
271 set_error(ERR_INVALID_ARGS, "Invalid arguments to gpg_import_key");
272 return -1;
273 }
274
275 log_debug("Importing GPG key from: %s", key_source);
276
277 /* Check if key_source is a file or key ID */
278 if (path_exists(key_source)) {
279 /* Import from file */
280 if (safe_snprintf(command, sizeof(command), "gpg --import '%s'", key_source) != 0) {
281 set_error(ERR_INVALID_ARGS, "GPG import command too long");
282 return -1;
283 }
284 } else {
285 /* Import from keyserver */
286 if (safe_snprintf(command, sizeof(command), "gpg --keyserver hkps://keys.openpgp.org --recv-keys %s",
287 key_source) != 0) {
288 set_error(ERR_INVALID_ARGS, "GPG keyserver command too long");
289 return -1;
290 }
291 }
292
293 /* Execute import command */
294 result = execute_gpg_command_in_env(gpg_config, command, output, sizeof(output));
295 if (result != 0) {
296 set_error(ERR_GPG_KEY_FAILED, "Failed to import GPG key: %s", output);
297 return -1;
298 }
299
300 log_info("Successfully imported GPG key from: %s", key_source);
301 return 0;
302 }
303
304 /* Export GPG public key for backup/sharing */
305 int gpg_export_public_key(gpg_config_t *gpg_config, const char *key_id,
306 char *output, size_t output_size) {
307 char command[256];
308
309 if (!gpg_config || !key_id || !output || output_size == 0) {
310 set_error(ERR_INVALID_ARGS, "Invalid arguments to gpg_export_public_key");
311 return -1;
312 }
313
314 if (safe_snprintf(command, sizeof(command), "gpg --armor --export %s", key_id) != 0) {
315 set_error(ERR_INVALID_ARGS, "GPG export command too long");
316 return -1;
317 }
318
319 return execute_gpg_command_in_env(gpg_config, command, output, output_size);
320 }
321
322 /* List available GPG keys */
323 int gpg_list_keys(gpg_config_t *gpg_config, char *output, size_t output_size) {
324 if (!gpg_config || !output || output_size == 0) {
325 set_error(ERR_INVALID_ARGS, "Invalid arguments to gpg_list_keys");
326 return -1;
327 }
328
329 return execute_gpg_command_in_env(gpg_config, "gpg --list-keys --with-colons", output, output_size);
330 }
331
332 /* Validate GPG key exists and is usable */
333 int gpg_validate_key(gpg_config_t *gpg_config, const char *key_id) {
334 char command[256];
335 char output[512];
336 int result;
337
338 if (!gpg_config || !key_id) {
339 set_error(ERR_INVALID_ARGS, "Invalid arguments to gpg_validate_key");
340 return -1;
341 }
342
343 log_debug("Validating GPG key: %s", key_id);
344
345 /* Check if key exists in keyring */
346 if (safe_snprintf(command, sizeof(command), "gpg --list-secret-keys %s", key_id) != 0) {
347 set_error(ERR_INVALID_ARGS, "GPG validation command too long");
348 return -1;
349 }
350
351 result = execute_gpg_command_in_env(gpg_config, command, output, sizeof(output));
352 if (result != 0) {
353 set_error(ERR_GPG_KEY_NOT_FOUND, "GPG key not found: %s", key_id);
354 return -1;
355 }
356
357 log_debug("GPG key validation passed: %s", key_id);
358 return 0;
359 }
360
361 /* Configure git GPG signing */
362 int gpg_configure_git_signing(gpg_config_t *gpg_config, const account_t *account, git_scope_t scope) {
363 if (!gpg_config || !account) {
364 set_error(ERR_INVALID_ARGS, "Invalid arguments to gpg_configure_git_signing");
365 return -1;
366 }
367
368 /* Skip if GPG signing not enabled */
369 if (!account->gpg_signing_enabled) {
370 log_debug("GPG signing not enabled for account: %s", account->name);
371
372 /* Disable git signing */
373 if (git_set_config_value("commit.gpgsign", "false", scope) != 0) {
374 log_warning("Failed to disable git GPG signing");
375 }
376 return 0;
377 }
378
379 log_info("Configuring git GPG signing for account: %s", account->name);
380
381 /* Set signing key */
382 if (git_set_config_value("user.signingkey", account->gpg_key_id, scope) != 0) {
383 set_error(ERR_GIT_CONFIG_FAILED, "Failed to set git signing key");
384 return -1;
385 }
386
387 /* Enable GPG signing */
388 if (git_set_config_value("commit.gpgsign", "true", scope) != 0) {
389 set_error(ERR_GIT_CONFIG_FAILED, "Failed to enable git GPG signing");
390 return -1;
391 }
392
393 /* GNUPGHOME is already set via gpg_set_environment() - no need to override
394 * gpg.program. git inherits the env var and gpg uses it automatically.
395 * Setting gpg.program to "gpg --homedir ..." breaks because git execs it
396 * as a single binary path, not a shell command. */
397 git_unset_config_value("gpg.program", scope);
398
399 log_info("Git GPG signing configured successfully for account: %s", account->name);
400 return 0;
401 }
402
403 /* Test GPG signing by creating a test signature */
404 int gpg_test_signing(gpg_config_t *gpg_config, const char *key_id) {
405 char command[512];
406 char output[1024];
407 int result;
408
409 if (!gpg_config || !key_id) {
410 set_error(ERR_INVALID_ARGS, "Invalid arguments to gpg_test_signing");
411 return -1;
412 }
413
414 log_debug("Testing GPG signing with key: %s", key_id);
415
416 /* Create test signature */
417 if (safe_snprintf(command, sizeof(command),
418 "echo 'GPG signing test' | gpg --clearsign --local-user %s", key_id) != 0) {
419 set_error(ERR_INVALID_ARGS, "GPG test command too long");
420 return -1;
421 }
422
423 result = execute_gpg_command_in_env(gpg_config, command, output, sizeof(output));
424 if (result != 0) {
425 set_error(ERR_GPG_SIGNING_FAILED, "GPG signing test failed: %s", output);
426 return -1;
427 }
428
429 /* Verify the signature contains expected content */
430 if (strstr(output, "BEGIN PGP SIGNED MESSAGE") == NULL) {
431 set_error(ERR_GPG_SIGNING_FAILED, "GPG signing test produced invalid output");
432 return -1;
433 }
434
435 log_debug("GPG signing test passed for key: %s", key_id);
436 return 0;
437 }
438
439 /* Generate new GPG key for account */
440 int gpg_generate_key(gpg_config_t *gpg_config, const account_t *account) {
441 char command[1024];
442 char key_params[512];
443 char output[2048];
444 int result;
445
446 if (!gpg_config || !account) {
447 set_error(ERR_INVALID_ARGS, "Invalid arguments to gpg_generate_key");
448 return -1;
449 }
450
451 log_info("Generating new GPG key for account: %s", account->name);
452
453 /* Create key generation parameters */
454 if (safe_snprintf(key_params, sizeof(key_params),
455 "Key-Type: RSA\n"
456 "Key-Length: 4096\n"
457 "Subkey-Type: RSA\n"
458 "Subkey-Length: 4096\n"
459 "Name-Real: %s\n"
460 "Name-Email: %s\n"
461 "Expire-Date: 2y\n"
462 "%%commit\n"
463 "%%echo done\n",
464 account->name, account->email) != 0) {
465 set_error(ERR_INVALID_ARGS, "GPG key parameters too long");
466 return -1;
467 }
468
469 /* Generate key */
470 if (safe_snprintf(command, sizeof(command), "echo '%s' | gpg --batch --generate-key", key_params) != 0) {
471 set_error(ERR_INVALID_ARGS, "GPG generation command too long");
472 return -1;
473 }
474
475 result = execute_gpg_command_in_env(gpg_config, command, output, sizeof(output));
476 if (result != 0) {
477 set_error(ERR_GPG_KEY_FAILED, "Failed to generate GPG key: %s", output);
478 return -1;
479 }
480
481 log_info("Successfully generated GPG key for account: %s", account->name);
482 return 0;
483 }
484
485 /* Set environment variables for GPG operation */
486 int gpg_set_environment(const gpg_config_t *gpg_config) {
487 if (!gpg_config) {
488 set_error(ERR_INVALID_ARGS, "Invalid arguments to gpg_set_environment");
489 return -1;
490 }
491
492 /* Set GNUPGHOME if using isolated mode */
493 if (gpg_config->mode == GPG_MODE_ISOLATED && strlen(gpg_config->gnupg_home) > 0) {
494 if (setenv("GNUPGHOME", gpg_config->gnupg_home, 1) != 0) {
495 set_system_error(ERR_SYSTEM_CALL, "Failed to set GNUPGHOME environment variable");
496 return -1;
497 }
498
499 log_debug("Set GNUPGHOME environment variable: %s", gpg_config->gnupg_home);
500 }
501
502 return 0;
503 }
504
505 /* Internal helper functions */
506
507 /* Create isolated GNUPGHOME directory with proper permissions */
508 static int create_isolated_gnupg_home_dir(const char *gnupg_home) {
509 if (!gnupg_home) {
510 set_error(ERR_INVALID_ARGS, "NULL gnupg_home path");
511 return -1;
512 }
513
514 /* Create directory with 700 permissions */
515 if (create_directory_recursive(gnupg_home, 0700) != 0) {
516 set_error(ERR_FILE_IO, "Failed to create GNUPGHOME directory: %s", gnupg_home);
517 return -1;
518 }
519
520 /* Validate permissions */
521 if (validate_gnupg_home_permissions(gnupg_home) != 0) {
522 return -1;
523 }
524
525 log_debug("Created isolated GNUPGHOME directory: %s", gnupg_home);
526 return 0;
527 }
528
529 /* Execute GPG command in specified environment */
530 static int execute_gpg_command_in_env(const gpg_config_t *gpg_config,
531 const char *command, char *output, size_t output_size) {
532 char full_command[1024];
533 FILE *fp;
534 int status;
535 size_t bytes_read;
536
537 if (!gpg_config || !command || !output || output_size == 0) {
538 set_error(ERR_INVALID_ARGS, "Invalid arguments to execute_gpg_command_in_env");
539 return -1;
540 }
541
542 /* Prepare command with GNUPGHOME if needed */
543 if (gpg_config->mode == GPG_MODE_ISOLATED && strlen(gpg_config->gnupg_home) > 0) {
544 if (safe_snprintf(full_command, sizeof(full_command),
545 "GNUPGHOME='%s' %s 2>&1", gpg_config->gnupg_home, command) != 0) {
546 set_error(ERR_INVALID_ARGS, "GPG command too long");
547 return -1;
548 }
549 } else {
550 if (safe_snprintf(full_command, sizeof(full_command), "%s 2>&1", command) != 0) {
551 set_error(ERR_INVALID_ARGS, "GPG command too long");
552 return -1;
553 }
554 }
555
556 log_debug("Executing GPG command: %s", full_command);
557
558 /* Execute command */
559 fp = popen(full_command, "r");
560 if (!fp) {
561 set_system_error(ERR_SYSTEM_COMMAND_FAILED, "Failed to execute GPG command");
562 return -1;
563 }
564
565 /* Read output */
566 bytes_read = fread(output, 1, output_size - 1, fp);
567 output[bytes_read] = '\0';
568
569 status = pclose(fp);
570 if (status != 0) {
571 log_debug("GPG command failed with status: %d, output: %s", status, output);
572 return -1;
573 }
574
575 log_debug("GPG command completed successfully");
576 return 0;
577 }
578
579 /* Copy GPG key from system keyring to isolated environment */
580 static int copy_key_from_system_keyring(const gpg_config_t *gpg_config, const char *key_id) {
581 char export_command[256];
582 char import_command[512];
583 char key_data[8192];
584 FILE *export_fp, *import_fp;
585 int export_status, import_status;
586 size_t bytes_read;
587
588 if (!gpg_config || !key_id) {
589 set_error(ERR_INVALID_ARGS, "Invalid arguments to copy_key_from_system_keyring");
590 return -1;
591 }
592
593 log_debug("Copying GPG key from system keyring: %s", key_id);
594
595 /* Export key from system keyring */
596 if (safe_snprintf(export_command, sizeof(export_command),
597 "gpg --armor --export-secret-keys %s 2>/dev/null", key_id) != 0) {
598 set_error(ERR_INVALID_ARGS, "Export command too long");
599 return -1;
600 }
601
602 export_fp = popen(export_command, "r");
603 if (!export_fp) {
604 set_system_error(ERR_SYSTEM_COMMAND_FAILED, "Failed to export GPG key");
605 return -1;
606 }
607
608 bytes_read = fread(key_data, 1, sizeof(key_data) - 1, export_fp);
609 key_data[bytes_read] = '\0';
610
611 export_status = pclose(export_fp);
612 if (export_status != 0 || bytes_read == 0) {
613 set_error(ERR_GPG_KEY_NOT_FOUND, "Failed to export GPG key from system keyring");
614 return -1;
615 }
616
617 /* Import key into isolated environment */
618 if (safe_snprintf(import_command, sizeof(import_command),
619 "GNUPGHOME='%s' gpg --batch --import 2>/dev/null", gpg_config->gnupg_home) != 0) {
620 set_error(ERR_INVALID_ARGS, "Import command too long");
621 return -1;
622 }
623
624 import_fp = popen(import_command, "w");
625 if (!import_fp) {
626 set_system_error(ERR_SYSTEM_COMMAND_FAILED, "Failed to start GPG import");
627 return -1;
628 }
629
630 fwrite(key_data, 1, bytes_read, import_fp);
631 import_status = pclose(import_fp);
632
633 if (import_status != 0) {
634 set_error(ERR_GPG_KEY_FAILED, "Failed to import GPG key into isolated environment");
635 return -1;
636 }
637
638 log_info("Successfully copied GPG key to isolated environment: %s", key_id);
639 return 0;
640 }
641
642 /* Validate GNUPGHOME directory permissions */
643 static int validate_gnupg_home_permissions(const char *gnupg_home) {
644 mode_t dir_mode;
645
646 if (!gnupg_home) {
647 set_error(ERR_INVALID_ARGS, "NULL gnupg_home path");
648 return -1;
649 }
650
651 if (get_file_permissions(gnupg_home, &dir_mode) != 0) {
652 set_error(ERR_FILE_IO, "Failed to check GNUPGHOME permissions: %s", gnupg_home);
653 return -1;
654 }
655
656 /* Check for 700 permissions */
657 if ((dir_mode & 0777) != 0700) {
658 set_error(ERR_PERMISSION_DENIED, "GNUPGHOME has insecure permissions: %o", dir_mode & 0777);
659 return -1;
660 }
661
662 log_debug("GNUPGHOME permissions validated: %s", gnupg_home);
663 return 0;
664 }
665
666 /* Set up GPG agent configuration for isolated environment */
667 static int setup_gpg_agent_config(const char *gnupg_home) {
668 char gpg_agent_conf_path[MAX_PATH_LEN];
669 FILE *conf_file;
670
671 if (!gnupg_home) {
672 set_error(ERR_INVALID_ARGS, "NULL gnupg_home path");
673 return -1;
674 }
675
676 /* Create gpg-agent.conf path */
677 if (safe_snprintf(gpg_agent_conf_path, sizeof(gpg_agent_conf_path),
678 "%s/gpg-agent.conf", gnupg_home) != 0) {
679 set_error(ERR_INVALID_PATH, "GPG agent config path too long");
680 return -1;
681 }
682
683 /* Create basic gpg-agent.conf */
684 conf_file = fopen(gpg_agent_conf_path, "w");
685 if (!conf_file) {
686 set_system_error(ERR_FILE_IO, "Failed to create gpg-agent.conf");
687 return -1;
688 }
689
690 fprintf(conf_file, "# GPG Agent configuration for gitswitch isolated environment\n");
691 fprintf(conf_file, "default-cache-ttl 3600\n");
692 fprintf(conf_file, "max-cache-ttl 7200\n");
693 fprintf(conf_file, "pinentry-program /usr/bin/pinentry-curses\n");
694
695 fclose(conf_file);
696
697 /* Set proper permissions */
698 if (chmod(gpg_agent_conf_path, 0600) != 0) {
699 set_system_error(ERR_PERMISSION_DENIED, "Failed to set gpg-agent.conf permissions");
700 return -1;
701 }
702
703 log_debug("Created GPG agent configuration: %s", gpg_agent_conf_path);
704 return 0;
705 }