ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
signing.c
Go to the documentation of this file.
1
7#include "signing.h"
8#include "agent.h"
9#include "../keys.h"
10#include "common.h"
11#include "util/string.h"
12#include "util/validation.h"
13#include "log/logging.h"
14#include "platform/system.h"
15
16#include <ctype.h>
17#include <errno.h>
18#include <stdio.h>
19#include <stdlib.h>
20#include <string.h>
21#include <unistd.h>
22
23#ifdef _WIN32
24#define SAFE_POPEN _popen
25#define SAFE_PCLOSE _pclose
26#else
27#define SAFE_POPEN popen
28#define SAFE_PCLOSE pclose
29#endif
30
44int gpg_sign_with_key(const char *key_id, const uint8_t *message, size_t message_len, uint8_t *signature_out,
45 size_t *signature_len_out) {
46 if (!key_id || !message || message_len == 0 || !signature_out || !signature_len_out) {
47 log_error("Invalid parameters to gpg_sign_with_key");
48 return -1;
49 }
50
51 char msg_path[512];
52 char sig_path[512];
53 int msg_fd = -1;
54 int result = -1;
55
56#ifdef _WIN32
57 // Windows: use GetTempPath + GetTempFileName with process ID
58 char temp_dir[MAX_PATH];
59 if (GetTempPathA(sizeof(temp_dir), temp_dir) == 0) {
60 log_error("Failed to get temp directory");
61 return -1;
62 }
63
64 char msg_prefix[32];
65 char sig_prefix[32];
66 safe_snprintf(msg_prefix, sizeof(msg_prefix), "asc_msg_%lu_", GetCurrentProcessId());
67 safe_snprintf(sig_prefix, sizeof(sig_prefix), "asc_sig_%lu_", GetCurrentProcessId());
68
69 if (GetTempFileNameA(temp_dir, msg_prefix, 0, msg_path) == 0) {
70 log_error("Failed to create temp message file");
71 return -1;
72 }
73 if (GetTempFileNameA(temp_dir, sig_prefix, 0, sig_path) == 0) {
74 log_error("Failed to create temp signature file");
75 unlink(msg_path);
76 return -1;
77 }
78
79 msg_fd = platform_open(msg_path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
80#else
81 // Unix: use mkstemp with process ID in template
82 safe_snprintf(msg_path, sizeof(msg_path), "/tmp/asciichat_msg_%d_XXXXXX", getpid());
83 safe_snprintf(sig_path, sizeof(sig_path), "/tmp/asciichat_sig_%d_XXXXXX", getpid());
84
85 msg_fd = mkstemp(msg_path);
86 if (msg_fd < 0) {
87 log_error("Failed to create temp message file: %s", SAFE_STRERROR(errno));
88 return -1;
89 }
90
91 // Create signature file path (will be created by gpg)
92 int sig_fd = mkstemp(sig_path);
93 if (sig_fd < 0) {
94 log_error("Failed to create temp signature file: %s", SAFE_STRERROR(errno));
95 close(msg_fd);
96 unlink(msg_path);
97 return -1;
98 }
99 close(sig_fd); // Close and let gpg overwrite it
100 unlink(sig_path); // Remove it so gpg can create it fresh
101#endif
102
103 if (msg_fd < 0) {
104 log_error("Failed to open temp message file");
105 goto cleanup;
106 }
107
108 // Write message to temp file
109 ssize_t written = write(msg_fd, message, message_len);
110 close(msg_fd);
111 msg_fd = -1;
112
113 if (written != (ssize_t)message_len) {
114 log_error("Failed to write message to temp file");
115 goto cleanup;
116 }
117
118 // Escape key ID for shell command (prevent injection)
119 char escaped_key_id[64];
120 if (!escape_path_for_shell(key_id, escaped_key_id, sizeof(escaped_key_id))) {
121 log_error("Failed to escape GPG key ID for shell command");
122 goto cleanup;
123 }
124
125 // Call gpg --detach-sign
126 char cmd[BUFFER_SIZE_LARGE];
127#ifdef _WIN32
128 safe_snprintf(cmd, sizeof(cmd), "gpg --local-user 0x%s --detach-sign --output \"%s\" \"%s\" 2>nul", escaped_key_id,
129 sig_path, msg_path);
130#else
131 safe_snprintf(cmd, sizeof(cmd), "gpg --local-user 0x%s --detach-sign --output \"%s\" \"%s\" 2>/dev/null",
132 escaped_key_id, sig_path, msg_path);
133#endif
134
135 log_debug("Signing with GPG: %s", cmd);
136 int status = system(cmd);
137 if (status != 0) {
138 log_error("GPG signing failed (exit code %d)", status);
139 goto cleanup;
140 }
141
142 // Read signature file
143 FILE *sig_fp = fopen(sig_path, "rb");
144 if (!sig_fp) {
145 log_error("Failed to open signature file: %s", SAFE_STRERROR(errno));
146 goto cleanup;
147 }
148
149 fseek(sig_fp, 0, SEEK_END);
150 long sig_size = ftell(sig_fp);
151 fseek(sig_fp, 0, SEEK_SET);
152
153 if (sig_size <= 0 || sig_size > 512) {
154 log_error("Invalid signature size: %ld bytes", sig_size);
155 fclose(sig_fp);
156 goto cleanup;
157 }
158
159 size_t bytes_read = fread(signature_out, 1, sig_size, sig_fp);
160 fclose(sig_fp);
161
162 if (bytes_read != (size_t)sig_size) {
163 log_error("Failed to read signature file");
164 goto cleanup;
165 }
166
167 *signature_len_out = sig_size;
168 log_info("GPG signature created successfully (%zu bytes)", *signature_len_out);
169 result = 0;
170
171cleanup:
172 if (msg_fd >= 0) {
173 close(msg_fd);
174 }
175 unlink(msg_path);
176 unlink(sig_path);
177 return result;
178}
179
180int gpg_sign_detached_ed25519(const char *key_id, const uint8_t *message, size_t message_len,
181 uint8_t signature_out[64]) {
182 log_info("gpg_sign_detached_ed25519: Signing with key ID %s (fallback mode)", key_id);
183
184 // Get OpenPGP signature packet from gpg --detach-sign
185 uint8_t openpgp_signature[512];
186 size_t openpgp_len = 0;
187
188 int result = gpg_sign_with_key(key_id, message, message_len, openpgp_signature, &openpgp_len);
189 if (result != 0) {
190 log_error("GPG detached signing failed for key %s", key_id);
191 return -1;
192 }
193
194 log_debug("gpg_sign_with_key returned %zu bytes", openpgp_len);
195
196 if (openpgp_len < 10) {
197 log_error("GPG signature too short: %zu bytes", openpgp_len);
198 return -1;
199 }
200
201 log_debug("Parsing OpenPGP signature packet (%zu bytes) to extract Ed25519 signature", openpgp_len);
202
203 // Parse OpenPGP signature packet format
204 // Reference: RFC 4880 Section 5.2 (Signature Packet)
205 // Format: [header][version][type][algo][hash-algo][...][signature-data]
206 size_t offset = 0;
207
208 // Parse packet header
209 uint8_t tag = openpgp_signature[offset++];
210 size_t packet_len = 0;
211
212 if ((tag & 0x40) == 0) {
213 // Old format packet
214 uint8_t length_type = tag & 0x03;
215 if (length_type == 0) {
216 packet_len = openpgp_signature[offset++];
217 } else if (length_type == 1) {
218 packet_len = (openpgp_signature[offset] << 8) | openpgp_signature[offset + 1];
219 offset += 2;
220 } else if (length_type == 2) {
221 packet_len = (openpgp_signature[offset] << 24) | (openpgp_signature[offset + 1] << 16) |
222 (openpgp_signature[offset + 2] << 8) | openpgp_signature[offset + 3];
223 offset += 4;
224 } else {
225 log_error("Unsupported old-format packet length type: %d", length_type);
226 return -1;
227 }
228 } else {
229 // New format packet
230 uint8_t length_byte = openpgp_signature[offset++];
231 if (length_byte < 192) {
232 packet_len = length_byte;
233 } else if (length_byte < 224) {
234 packet_len = ((length_byte - 192) << 8) + openpgp_signature[offset++] + 192;
235 } else if (length_byte == 255) {
236 packet_len = (openpgp_signature[offset] << 24) | (openpgp_signature[offset + 1] << 16) |
237 (openpgp_signature[offset + 2] << 8) | openpgp_signature[offset + 3];
238 offset += 4;
239 } else {
240 log_error("Unsupported new-format packet length encoding: %d", length_byte);
241 return -1;
242 }
243 }
244
245 if (offset + packet_len > openpgp_len) {
246 log_error("Packet length exceeds signature size: %zu + %zu > %zu", offset, packet_len, openpgp_len);
247 return -1;
248 }
249
250 log_debug("Signature packet: offset=%zu, length=%zu", offset, packet_len);
251
252 // Parse signature packet body
253 // Skip: version (1), sig_type (1), pub_algo (1), hash_algo (1)
254 if (offset + 4 > openpgp_len) {
255 log_error("Signature packet too short for header");
256 return -1;
257 }
258
259 uint8_t version = openpgp_signature[offset++];
260 uint8_t sig_type = openpgp_signature[offset++];
261 uint8_t pub_algo = openpgp_signature[offset++];
262 uint8_t hash_algo = openpgp_signature[offset++];
263
264 log_debug("Signature: version=%d, type=%d, algo=%d, hash=%d", version, sig_type, pub_algo, hash_algo);
265
266 // Verify algorithm is Ed25519 (22 = EdDSA)
267 if (pub_algo != 22) {
268 log_error("Expected EdDSA algorithm (22), got %d", pub_algo);
269 return -1;
270 }
271
272 // For v4 signatures: skip hashed subpackets
273 if (version == 4) {
274 if (offset + 2 > openpgp_len) {
275 log_error("Cannot read hashed subpacket length");
276 return -1;
277 }
278 uint16_t hashed_len = (openpgp_signature[offset] << 8) | openpgp_signature[offset + 1];
279 offset += 2;
280 offset += hashed_len; // Skip hashed subpackets
281
282 if (offset + 2 > openpgp_len) {
283 log_error("Cannot read unhashed subpacket length");
284 return -1;
285 }
286 uint16_t unhashed_len = (openpgp_signature[offset] << 8) | openpgp_signature[offset + 1];
287 offset += 2;
288 offset += unhashed_len; // Skip unhashed subpackets
289
290 // Skip left 16 bits of signed hash value
291 if (offset + 2 > openpgp_len) {
292 log_error("Cannot read hash left bits");
293 return -1;
294 }
295 offset += 2;
296 }
297
298 // Now we're at the signature data (MPI format for Ed25519)
299 // Ed25519 signature is: r (32 bytes) || s (32 bytes) = 64 bytes total
300 // In OpenPGP, each MPI is encoded as: [2-byte bit count][data]
301
302 if (offset + 2 > openpgp_len) {
303 log_error("Cannot read MPI bit count for R");
304 return -1;
305 }
306
307 uint16_t r_bits = (openpgp_signature[offset] << 8) | openpgp_signature[offset + 1];
308 offset += 2;
309 size_t r_bytes = (r_bits + 7) / 8;
310
311 log_debug("R: %d bits (%zu bytes)", r_bits, r_bytes);
312
313 if (r_bytes != 32) {
314 log_error("Expected 32-byte R value, got %zu bytes", r_bytes);
315 return -1;
316 }
317
318 if (offset + r_bytes > openpgp_len) {
319 log_error("R value exceeds packet size");
320 return -1;
321 }
322
323 memcpy(signature_out, &openpgp_signature[offset], 32);
324 offset += r_bytes;
325
326 // Read S value
327 if (offset + 2 > openpgp_len) {
328 log_error("Cannot read MPI bit count for S");
329 return -1;
330 }
331
332 uint16_t s_bits = (openpgp_signature[offset] << 8) | openpgp_signature[offset + 1];
333 offset += 2;
334 size_t s_bytes = (s_bits + 7) / 8;
335
336 log_debug("S: %d bits (%zu bytes)", s_bits, s_bytes);
337
338 if (s_bytes != 32) {
339 log_error("Expected 32-byte S value, got %zu bytes", s_bytes);
340 return -1;
341 }
342
343 if (offset + s_bytes > openpgp_len) {
344 log_error("S value exceeds packet size");
345 return -1;
346 }
347
348 memcpy(signature_out + 32, &openpgp_signature[offset], 32);
349
350 log_info("Successfully extracted 64-byte Ed25519 signature from OpenPGP packet");
351
352 // Debug: Log signature components
353 char hex_r[65], hex_s[65];
354 for (int i = 0; i < 32; i++) {
355 safe_snprintf(hex_r + i * 2, 3, "%02x", signature_out[i]);
356 safe_snprintf(hex_s + i * 2, 3, "%02x", signature_out[i + 32]);
357 }
358 hex_r[64] = hex_s[64] = '\0';
359 log_debug("Signature R (first 32 bytes): %s", hex_r);
360 log_debug("Signature S (last 32 bytes): %s", hex_s);
361
362 return 0;
363}
364
GPG agent connection and communication interface.
#define BUFFER_SIZE_LARGE
Large buffer size (1024 bytes)
unsigned short uint16_t
Definition common.h:57
#define SAFE_STRERROR(errnum)
Definition common.h:385
unsigned char uint8_t
Definition common.h:56
int gpg_sign_detached_ed25519(const char *key_id, const uint8_t *message, size_t message_len, uint8_t signature_out[64])
Sign message with GPG and extract raw Ed25519 signature.
Definition signing.c:180
int gpg_sign_with_key(const char *key_id, const uint8_t *message, size_t message_len, uint8_t *signature_out, size_t *signature_len_out)
Sign a message using GPG key (via gpg –detach-sign)
Definition signing.c:44
#define log_error(...)
Log an ERROR message.
#define log_info(...)
Log an INFO message.
#define log_debug(...)
Log a DEBUG message.
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe version of snprintf that ensures null termination.
int platform_open(const char *pathname, int flags,...)
Safe file open (open replacement)
int errno
bool escape_path_for_shell(const char *path, char *out_buffer, size_t out_buffer_size)
Escape a path for safe use in shell commands (auto-platform)
Definition string.c:213
📝 Logging API with multiple log levels and terminal output control
GPG message signing interface.
Cross-platform system functions interface for ascii-chat.
🔤 String Manipulation and Shell Escaping Utilities
Common validation macros to reduce duplication in protocol handlers.