24#define SAFE_POPEN _popen
25#define SAFE_PCLOSE _pclose
27#define SAFE_POPEN popen
28#define SAFE_PCLOSE pclose
41static int gpg_export_public_key(
const char *key_id,
uint8_t *public_key_out) {
42 if (!key_id || !public_key_out) {
43 log_error(
"Invalid arguments to gpg_export_public_key");
50 log_error(
"Failed to escape GPG key ID for shell command");
56 safe_snprintf(temp_path,
sizeof(temp_path),
"/tmp/asciichat_gpg_export_%d_XXXXXX", getpid());
57 int temp_fd = mkstemp(temp_path);
66 safe_snprintf(cmd,
sizeof(cmd),
"gpg --export 0x%s > \"%s\" 2>/dev/null", escaped_key_id, temp_path);
68 log_debug(
"Running GPG export command: gpg --export 0x%s", key_id);
69 int result = system(cmd);
71 log_error(
"Failed to export GPG public key for key ID: %s (exit code: %d)", key_id, result);
75 log_debug(
"GPG export completed successfully");
78 FILE *fp = fopen(temp_path,
"rb");
80 log_error(
"Failed to open exported GPG key file");
87 size_t bytes_read = fread(packet_data, 1,
sizeof(packet_data), fp);
91 if (bytes_read == 0) {
92 log_error(
"GPG export produced empty output - key may not exist");
96 log_debug(
"Read %zu bytes from GPG export", bytes_read);
111 while (offset < bytes_read) {
112 uint8_t tag = packet_data[offset];
115 bool is_public_key =
false;
116 size_t packet_len = 0;
118 if ((tag & 0x80) == 0) {
124 if ((tag & 0x40) == 0) {
126 uint8_t packet_type = (tag >> 2) & 0x0F;
127 is_public_key = (packet_type == 6 || packet_type == 14);
129 uint8_t length_type = tag & 0x03;
132 if (length_type == 0) {
133 packet_len = packet_data[offset++];
134 }
else if (length_type == 1) {
135 packet_len = (packet_data[offset] << 8) | packet_data[offset + 1];
137 }
else if (length_type == 2) {
138 packet_len = (packet_data[offset] << 24) | (packet_data[offset + 1] << 16) | (packet_data[offset + 2] << 8) |
139 packet_data[offset + 3];
147 uint8_t packet_type = tag & 0x3F;
148 is_public_key = (packet_type == 6 || packet_type == 14);
152 if (offset >= bytes_read)
154 uint8_t first_len = packet_data[offset++];
156 if (first_len < 192) {
157 packet_len = first_len;
158 }
else if (first_len < 224) {
159 if (offset >= bytes_read)
161 packet_len = ((first_len - 192) << 8) + packet_data[offset++] + 192;
162 }
else if (first_len == 255) {
163 if (offset + 4 > bytes_read)
165 packet_len = (packet_data[offset] << 24) | (packet_data[offset + 1] << 16) | (packet_data[offset + 2] << 8) |
166 packet_data[offset + 3];
174 if (!is_public_key || packet_len == 0 || offset + packet_len > bytes_read) {
175 offset += packet_len;
180 size_t packet_start = offset;
183 if (packet_data[offset] != 0x04) {
184 offset += packet_len;
192 if (offset >= packet_start + packet_len) {
193 offset = packet_start + packet_len;
197 uint8_t algorithm = packet_data[offset++];
198 if (algorithm != 22) {
199 offset = packet_start + packet_len;
204 if (offset >= packet_start + packet_len) {
205 offset = packet_start + packet_len;
209 uint8_t oid_len = packet_data[offset++];
214 if (offset + 2 > packet_start + packet_len) {
215 offset = packet_start + packet_len;
219 uint16_t mpi_bits = (packet_data[offset] << 8) | packet_data[offset + 1];
224 size_t mpi_bytes = (mpi_bits + 7) / 8;
226 if (offset + mpi_bytes > packet_start + packet_len) {
227 offset = packet_start + packet_len;
232 if (mpi_bytes == 33 && packet_data[offset] == 0x40) {
234 memcpy(public_key_out, &packet_data[offset + 1], 32);
235 log_info(
"Extracted Ed25519 public key from gpg --export (fallback method)");
237 }
else if (mpi_bytes == 32) {
239 memcpy(public_key_out, &packet_data[offset], 32);
240 log_info(
"Extracted Ed25519 public key from gpg --export (fallback method)");
244 offset = packet_start + packet_len;
247 log_error(
"Failed to find Ed25519 public key in GPG export data");
252 if (!key_id || !public_key_out) {
253 log_error(
"Invalid arguments to gpg_get_public_key");
260 log_error(
"Invalid GPG key ID format - contains unsafe characters: %s", key_id);
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);
275 log_error(
"Failed to escape GPG key ID for shell command");
282 safe_snprintf(cmd,
sizeof(cmd),
"gpg --list-keys --with-keygrip --with-colons 0x%s 2>nul", escaped_key_id);
284 safe_snprintf(cmd,
sizeof(cmd),
"gpg --list-keys --with-keygrip --with-colons 0x%s 2>/dev/null", escaped_key_id);
288 log_error(
"Failed to run gpg command - GPG may not be installed");
290 log_error(
"To install GPG on Windows, download Gpg4win from:");
291 log_error(
" https://www.gpg4win.org/download.html");
292#elif defined(__APPLE__)
293 log_error(
"To install GPG on macOS, use Homebrew:");
297 log_error(
" Debian/Ubuntu: sudo apt-get install gnupg");
298 log_error(
" Fedora/RHEL: sudo dnf install gnupg2");
299 log_error(
" Arch Linux: sudo pacman -S gnupg");
300 log_error(
" Alpine Linux: sudo apk add gnupg");
306 char found_keygrip[128] = {0};
307 bool found_key =
false;
311 while (fgets(line,
sizeof(line), fp)) {
312 if (strncmp(line,
"pub:", 4) == 0) {
315 }
else if (found_key && strncmp(line,
"grp:", 4) == 0) {
319 const char *grp_start = line + 4;
321 while (*grp_start && colon_count < 8) {
322 if (*grp_start ==
':') {
328 if (colon_count == 8) {
329 const char *grp_end = strchr(grp_start,
':');
331 size_t grp_len = grp_end - grp_start;
332 if (grp_len <
sizeof(found_keygrip)) {
333 memcpy(found_keygrip, grp_start, grp_len);
334 found_keygrip[grp_len] =
'\0';
348 if (!found_key || strlen(found_keygrip) == 0) {
349 log_error(
"Could not find GPG key with ID: %s", key_id);
353 log_debug(
"Found keygrip for key %s: %s", key_id, found_keygrip);
357 if (agent_sock < 0) {
358 log_info(
"GPG agent not available, falling back to gpg --export for public key extraction");
360 int export_result = gpg_export_public_key(key_id, public_key_out);
361 if (export_result == 0) {
362 log_info(
"Successfully extracted public key using fallback method");
364 log_error(
"Fallback public key extraction failed for key ID: %s", key_id);
366 return export_result;
370 char readkey_cmd[256];
371 safe_snprintf(readkey_cmd,
sizeof(readkey_cmd),
"READKEY %s\n", found_keygrip);
375 log_error(
"Failed to send READKEY command to GPG agent");
382 memset(response, 0,
sizeof(response));
383 ssize_t bytes_read =
platform_pipe_read(agent_sock, (
unsigned char *)response,
sizeof(response) - 1);
387 if (bytes_read <= 0) {
388 log_error(
"Failed to read READKEY response from GPG agent");
395 const char *q_marker = strstr(response,
"(1:q");
397 log_warn(
"Failed to find public key (1:q) in GPG agent READKEY response, trying gpg --export fallback");
398 log_debug(
"Response was: %.*s", (
int)(bytes_read < 200 ? bytes_read : 200), response);
402 int export_result = gpg_export_public_key(key_id, public_key_out);
403 if (export_result == 0) {
404 log_info(
"Successfully extracted public key using gpg --export fallback");
406 log_error(
"Fallback public key extraction failed for key ID: %s", key_id);
408 return export_result;
412 const char *len_start = q_marker + 4;
415 char *colon = strchr(len_start,
':');
417 log_error(
"Malformed S-expression: missing colon after length");
421 size_t key_len = strtoul(len_start, NULL, 10);
423 log_error(
"Unexpected Ed25519 public key length: %zu bytes (expected 33)", key_len);
428 const unsigned char *binary_start = (
const unsigned char *)(colon + 1);
431 if (binary_start[0] != 0x40) {
432 log_error(
"Invalid Ed25519 public key prefix: 0x%02x (expected 0x40)", binary_start[0]);
437 memcpy(public_key_out, binary_start + 1, 32);
439 log_info(
"Extracted Ed25519 public key from GPG agent via READKEY command");
GPG agent connection and communication interface.
GPG public key export interface.
#define BUFFER_SIZE_XXXLARGE
Extra extra extra large buffer size (8192 bytes)
#define BUFFER_SIZE_LARGE
Large buffer size (1024 bytes)
#define BUFFER_SIZE_XLARGE
Extra large buffer size (2048 bytes)
#define BUFFER_SIZE_MEDIUM
Medium buffer size (512 bytes)
#define SAFE_STRNCPY(dst, src, size)
#define SAFE_STRERROR(errnum)
int gpg_get_public_key(const char *key_id, uint8_t *public_key_out, char *keygrip_out)
Get public key from GPG keyring by key ID.
int gpg_agent_connect(void)
Connect to 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_info(...)
Log an INFO message.
#define log_debug(...)
Log a DEBUG message.
bool escape_shell_single_quotes(const char *str, char *out_buffer, size_t out_buffer_size)
Escape a string for safe use in shell commands (single quotes)
bool validate_shell_safe(const char *str, const char *allowed_chars)
Validate that a string contains only safe characters for shell commands.
📝 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
Common validation macros to reduce duplication in protocol handlers.