23#define SAFE_POPEN _popen
24#define SAFE_PCLOSE _pclose
26#include <sys/socket.h>
29#define SAFE_POPEN popen
30#define SAFE_PCLOSE pclose
34#define GPG_AGENT_MAX_RESPONSE 8192
39static int get_agent_socket_path(
char *path_out,
size_t path_size) {
43 FILE *fp =
SAFE_POPEN(
"gpgconf --list-dirs agent-socket 2>nul",
"r");
45 if (fgets(path_out, path_size, fp)) {
47 size_t len = strlen(path_out);
48 if (len > 0 && path_out[len - 1] ==
'\n') {
49 path_out[len - 1] =
'\0';
60 safe_snprintf(path_out, path_size,
"%s\\gnupg\\S.gpg-agent", appdata);
62 log_error(
"Could not determine APPDATA directory");
67 FILE *fp =
SAFE_POPEN(
"gpgconf --list-dirs agent-socket 2>/dev/null",
"r");
69 if (fgets(path_out, path_size, fp)) {
71 size_t len = strlen(path_out);
72 if (len > 0 && path_out[len - 1] ==
'\n') {
73 path_out[len - 1] =
'\0';
84 safe_snprintf(path_out, path_size,
"%s/S.gpg-agent", gnupg_home);
88 log_error(
"Could not determine home directory");
91 safe_snprintf(path_out, path_size,
"%s/.gnupg/S.gpg-agent", home);
103static int read_agent_line(HANDLE pipe,
char *buf,
size_t buf_size) {
105 while (pos < buf_size - 1) {
108 if (!ReadFile(pipe, &c, 1, &bytes_read, NULL) || bytes_read != 1) {
109 if (GetLastError() == ERROR_BROKEN_PIPE) {
110 log_error(
"GPG agent connection closed");
112 log_error(
"Error reading from GPG agent: %lu", GetLastError());
125 log_error(
"GPG agent response too long");
129static int read_agent_line(
int sock,
char *buf,
size_t buf_size) {
131 while (pos < buf_size - 1) {
133 ssize_t n = recv(sock, &c, 1, 0);
136 log_error(
"GPG agent connection closed");
151 log_error(
"GPG agent response too long");
160static int send_agent_command(HANDLE pipe,
const char *command) {
161 size_t len = strlen(command);
162 char *cmd_with_newline;
164 if (!cmd_with_newline) {
165 log_error(
"Failed to allocate memory for command");
169 memcpy(cmd_with_newline, command, len);
170 cmd_with_newline[len] =
'\n';
171 cmd_with_newline[len + 1] =
'\0';
174 BOOL result = WriteFile(pipe, cmd_with_newline, (DWORD)(len + 1), &
bytes_written, NULL);
178 log_error(
"Failed to send command to GPG agent: %lu", GetLastError());
185static int send_agent_command(
int sock,
const char *command) {
186 size_t len = strlen(command);
187 char *cmd_with_newline;
189 if (!cmd_with_newline) {
190 log_error(
"Failed to allocate memory for command");
194 memcpy(cmd_with_newline, command, len);
195 cmd_with_newline[len] =
'\n';
196 cmd_with_newline[len + 1] =
'\0';
198 ssize_t sent = send(sock, cmd_with_newline, len + 1, 0);
201 if (sent != (ssize_t)(len + 1)) {
202 log_error(
"Failed to send command to GPG agent");
213static bool is_ok_response(
const char *line) {
214 return strncmp(line,
"OK", 2) == 0;
221 if (get_agent_socket_path(pipe_path,
sizeof(pipe_path)) != 0) {
222 log_error(
"Failed to get GPG agent pipe path");
226 log_debug(
"Connecting to GPG agent at: %s", pipe_path);
229 if (!WaitNamedPipeA(pipe_path, 5000)) {
230 log_error(
"GPG agent pipe not available: %lu", GetLastError());
234 HANDLE pipe = CreateFileA(pipe_path, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
236 if (pipe == INVALID_HANDLE_VALUE) {
237 log_error(
"Failed to connect to GPG agent pipe: %lu", GetLastError());
242 DWORD mode = PIPE_READMODE_BYTE;
243 if (!SetNamedPipeHandleState(pipe, &mode, NULL, NULL)) {
244 log_error(
"Failed to set pipe mode: %lu", GetLastError());
251 if (read_agent_line(pipe, response,
sizeof(response)) != 0) {
252 log_error(
"Failed to read GPG agent greeting");
257 if (!is_ok_response(response)) {
258 log_error(
"Unexpected GPG agent greeting: %s", response);
263 log_debug(
"Connected to GPG agent successfully");
264 return (
int)(intptr_t)pipe;
269 if (get_agent_socket_path(socket_path,
sizeof(socket_path)) != 0) {
270 log_error(
"Failed to get GPG agent socket path");
274 log_debug(
"Connecting to GPG agent at: %s", socket_path);
276 int sock = socket(AF_UNIX, SOCK_STREAM, 0);
282 struct sockaddr_un addr;
283 memset(&addr, 0,
sizeof(addr));
284 addr.sun_family = AF_UNIX;
285 SAFE_STRNCPY(addr.sun_path, socket_path,
sizeof(addr.sun_path) - 1);
287 if (connect(sock, (
struct sockaddr *)&addr,
sizeof(addr)) != 0) {
295 if (read_agent_line(sock, response,
sizeof(response)) != 0) {
296 log_error(
"Failed to read GPG agent greeting");
301 if (!is_ok_response(response)) {
302 log_error(
"Unexpected GPG agent greeting: %s", response);
307 log_debug(
"Connected to GPG agent successfully");
311 if (send_agent_command(sock,
"OPTION pinentry-mode=loopback") != 0) {
312 log_warn(
"Failed to set loopback pinentry mode (continuing anyway)");
315 if (read_agent_line(sock, response,
sizeof(response)) != 0) {
316 log_warn(
"Failed to read OPTION command response (continuing anyway)");
317 }
else if (is_ok_response(response)) {
318 log_debug(
"Loopback pinentry mode enabled");
320 log_warn(
"Failed to enable loopback pinentry mode: %s (continuing anyway)", response);
330 if (handle_as_int >= 0) {
331 HANDLE pipe = (HANDLE)(intptr_t)handle_as_int;
332 send_agent_command(pipe,
"BYE");
339 send_agent_command(sock,
"BYE");
346 uint8_t *signature_out,
size_t *signature_len_out) {
347 if (handle_as_int < 0 || !keygrip || !message || !signature_out || !signature_len_out) {
348 log_error(
"Invalid arguments to gpg_agent_sign");
353 HANDLE handle = (HANDLE)(intptr_t)handle_as_int;
355 int handle = handle_as_int;
361 char sigkey_cmd[128];
362 safe_snprintf(sigkey_cmd,
sizeof(sigkey_cmd),
"SIGKEY %s", keygrip);
363 if (send_agent_command(handle, sigkey_cmd) != 0) {
364 log_error(
"Failed to send SIGKEY command");
368 if (read_agent_line(handle, response,
sizeof(response)) != 0) {
369 log_error(
"Failed to read SIGKEY response");
373 if (!is_ok_response(response)) {
374 log_error(
"SIGKEY failed: %s", response);
384 uint8_t hash[crypto_hash_sha512_BYTES];
385 crypto_hash_sha512(hash, message, message_len);
389 char sethash_cmd[256];
390 int offset =
safe_snprintf(sethash_cmd,
sizeof(sethash_cmd),
"SETHASH 10 ");
391 for (
size_t i = 0; i < crypto_hash_sha512_BYTES; i++) {
392 offset +=
safe_snprintf(sethash_cmd + offset,
sizeof(sethash_cmd) - (
size_t)offset,
"%02X", hash[i]);
395 log_debug(
"Sending SETHASH command with SHA512 hash");
396 if (send_agent_command(handle, sethash_cmd) != 0) {
397 log_error(
"Failed to send SETHASH command");
402 if (read_agent_line(handle, response,
sizeof(response)) != 0) {
403 log_error(
"Failed to read SETHASH response");
407 if (!is_ok_response(response)) {
408 log_error(
"SETHASH failed: %s", response);
413 if (send_agent_command(handle,
"PKSIGN") != 0) {
414 log_error(
"Failed to send PKSIGN command");
421 bool found_data =
false;
422 for (
int attempts = 0; attempts < 20; attempts++) {
423 if (read_agent_line(handle, response,
sizeof(response)) != 0) {
424 log_error(
"Failed to read PKSIGN response");
428 log_debug(
"PKSIGN response line %d: %s", attempts + 1, response);
431 if (response[0] ==
'S' && response[1] ==
' ') {
432 log_debug(
"Skipping PKSIGN status line: %s", response);
438 if (strncmp(response,
"ERR", 3) == 0) {
439 log_debug(
"Skipping PKSIGN error line (informational): %s", response);
444 if (response[0] ==
'D' && response[1] ==
' ') {
451 if (strncmp(response,
"OK", 2) == 0) {
452 log_warn(
"PKSIGN returned OK without data line");
457 if (strncmp(response,
"INQUIRE", 7) == 0) {
458 log_error(
"Unexpected INQUIRE after PKSIGN: %s", response);
463 log_warn(
"Unexpected PKSIGN response (attempt %d): %s", attempts + 1, response);
467 log_error(
"Expected D line from PKSIGN after %d attempts", 20);
478 size_t response_len = strlen(response);
479 size_t debug_len = response_len < 200 ? response_len : 200;
480 memcpy(debug_buf, response, debug_len);
481 debug_buf[debug_len] =
'\0';
482 log_debug(
"GPG agent D line (first 200 bytes): %s", debug_buf);
484 const char *data = response + 2;
491 const char *r_marker = strstr(data,
"(1:r32:");
493 log_error(
"Could not find r value marker in S-expression");
498 const char *r_data = r_marker + 7;
501 const char *s_marker = strstr(r_data + 32,
"(1:s32:");
503 log_error(
"Could not find s value marker in S-expression");
508 const char *s_data = s_marker + 7;
511 memcpy(signature_out, r_data, 32);
512 memcpy(signature_out + 32, s_data, 32);
514 *signature_len_out = 64;
518 for (
int i = 0; i < 64; i++) {
519 safe_snprintf(sig_hex + i * 2, 3,
"%02x", (
unsigned char)signature_out[i]);
522 log_debug(
"Extracted signature (64 bytes): %s", sig_hex);
525 if (read_agent_line(handle, response,
sizeof(response)) != 0) {
526 log_error(
"Failed to read final PKSIGN response");
530 if (!is_ok_response(response)) {
531 log_error(
"PKSIGN final response not OK: %s", response);
535 log_debug(
"Successfully signed message with GPG agent");
#define GPG_AGENT_MAX_RESPONSE
GPG agent connection and communication interface.
#define SAFE_STRNCPY(dst, src, size)
#define SAFE_MALLOC(size, cast)
#define SAFE_GETENV(name)
#define SAFE_STRERROR(errnum)
#define PLATFORM_MAX_PATH_LENGTH
bool gpg_agent_is_available(void)
Check if GPG agent is available.
int gpg_agent_connect(void)
Connect to gpg-agent.
int gpg_agent_sign(int handle_as_int, const char *keygrip, const uint8_t *message, size_t message_len, uint8_t *signature_out, size_t *signature_len_out)
Sign a message using GPG agent.
void gpg_agent_disconnect(int sock)
Disconnect from gpg-agent.
#define log_warn(...)
Log a WARN message.
#define log_error(...)
Log an ERROR message.
#define log_debug(...)
Log a DEBUG message.
📝 Logging API with multiple log levels and terminal output control
_Atomic uint64_t bytes_written
Cross-platform system functions interface for ascii-chat.
🔤 String Manipulation and Shell Escaping Utilities