ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
verification.c
Go to the documentation of this file.
1
10#include <ascii-chat/common.h>
13#include <ascii-chat/log/log.h>
17
18#include <ctype.h>
19#include <errno.h>
20#include <stdio.h>
21#include <stdlib.h>
22#include <string.h>
23#ifndef _WIN32
24#include <unistd.h>
25#endif
26
27#ifdef HAVE_LIBGCRYPT
28#include <gcrypt.h>
29#endif
30
31int gpg_verify_detached_ed25519(const char *key_id, const uint8_t *message, size_t message_len,
32 const uint8_t signature[64]) {
33 // Note: We don't use the raw signature parameter directly.
34 // Instead, we regenerate the OpenPGP signature using GPG (Ed25519 is deterministic).
35 (void)signature;
36
37 log_debug("gpg_verify_detached_ed25519: Verifying signature with key ID %s using gpg --verify", key_id);
38
39 // To verify with GPG, we need to:
40 // 1. Reconstruct the OpenPGP signature packet from the raw R||S signature
41 // 2. Write message and signature to temp files
42 // 3. Call gpg --verify
43
44 // First, reconstruct OpenPGP signature by signing the same message
45 // Since Ed25519 is deterministic, we should get the same OpenPGP packet
46 uint8_t openpgp_signature[512];
47 size_t openpgp_len = 0;
48
49 int sign_result = gpg_sign_with_key(key_id, message, message_len, openpgp_signature, &openpgp_len);
50 if (sign_result != 0) {
51 log_error("Failed to create reference signature for verification");
52 return -1;
53 }
54
55 // Now verify using gpg --verify
56 char msg_path[PLATFORM_MAX_PATH_LENGTH];
57 char sig_path[PLATFORM_MAX_PATH_LENGTH];
58 int msg_fd = -1;
59 int sig_fd = -1;
60
61 if (platform_create_temp_file(msg_path, sizeof(msg_path), "gpg_verify_msg", &msg_fd) != 0) {
62 log_error("Failed to create temporary message file");
63 return -1;
64 }
65
66 if (platform_create_temp_file(sig_path, sizeof(sig_path), "gpg_verify_sig", &sig_fd) != 0) {
68 log_error("Failed to create temporary signature file");
69 return -1;
70 }
71
72 // Write message
73 if (write(msg_fd, message, message_len) != (ssize_t)message_len) {
74 log_error("Failed to write message to temp file");
75 close(msg_fd);
76 close(sig_fd);
79 return -1;
80 }
81 close(msg_fd);
82
83 // Write OpenPGP signature
84 if (write(sig_fd, openpgp_signature, openpgp_len) != (ssize_t)openpgp_len) {
85 log_error("Failed to write signature to temp file");
86 close(sig_fd);
89 return -1;
90 }
91 close(sig_fd);
92
93 // Call gpg --verify
94 char cmd[1024];
95 safe_snprintf(cmd, sizeof(cmd), "gpg --verify '%s' '%s' 2>&1", sig_path, msg_path);
96 log_debug("Running: %s", cmd);
97
98 FILE *fp;
99 platform_popen("gpg_verify", cmd, "r", &fp);
100 if (!fp) {
101 log_error("Failed to run gpg --verify");
102 platform_unlink(msg_path);
103 platform_unlink(sig_path);
104 return -1;
105 }
106
107 char output[4096] = {0};
108 size_t output_len = fread(output, 1, sizeof(output) - 1, fp);
109 int exit_code = platform_pclose(&fp);
110
111 // Cleanup temp files
114
115 if (exit_code == 0) {
116 log_debug("GPG signature verification PASSED");
117 return 0;
118 } else {
119 log_error("GPG signature verification FAILED (exit code %d)", exit_code);
120 if (output_len > 0) {
121 log_debug("GPG output: %s", output);
122 }
123 return -1;
124 }
125}
126
127int gpg_verify_signature(const uint8_t *public_key, const uint8_t *message, size_t message_len,
128 const uint8_t *signature) {
129#ifdef HAVE_LIBGCRYPT
130 gcry_error_t err;
131 gcry_sexp_t s_pubkey = NULL;
132 gcry_sexp_t s_sig = NULL;
133 gcry_sexp_t s_data = NULL;
134
135 // Initialize libgcrypt if not already done
136 if (!gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) {
137 gcry_check_version(NULL);
138 gcry_control(GCRYCTL_DISABLE_SECMEM, 0);
139 gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
140 }
141
142 // Build public key S-expression: (public-key (ecc (curve Ed25519) (flags eddsa) (q %b)))
143 // Must include (flags eddsa) to match libgcrypt's Ed25519 test suite.
144 // See libgcrypt/tests/t-ed25519.c line 246-251.
145 err = gcry_sexp_build(&s_pubkey, NULL, "(public-key (ecc (curve Ed25519) (flags eddsa) (q %b)))", 32, public_key);
146 if (err) {
147 log_error("gpg_verify_signature: Failed to build public key S-expression: %s", gcry_strerror(err));
148 return -1;
149 }
150
151 // Build signature S-expression: (sig-val (eddsa (r %b) (s %b)))
152 // Signature is 64 bytes: first 32 bytes are R, last 32 bytes are S
153 err = gcry_sexp_build(&s_sig, NULL, "(sig-val (eddsa (r %b) (s %b)))", 32, signature, 32, signature + 32);
154 if (err) {
155 log_error("gpg_verify_signature: Failed to build signature S-expression: %s", gcry_strerror(err));
156 gcry_sexp_release(s_pubkey);
157 return -1;
158 }
159
160 // Build data S-expression with raw message
161 // According to libgcrypt's test suite (t-ed25519.c line 273),
162 // Ed25519 data should be: (data (value %b)) with no flags.
163 // The (flags eddsa) belongs in the key S-expression above, not in the data.
164 // GPG agent's internal format is different - this is the correct libgcrypt API usage.
165 err = gcry_sexp_build(&s_data, NULL, "(data (value %b))", message_len, message);
166 if (err) {
167 log_error("gpg_verify_signature: Failed to build data S-expression: %s", gcry_strerror(err));
168 gcry_sexp_release(s_pubkey);
169 gcry_sexp_release(s_sig);
170 return -1;
171 }
172
173 // Debug logging
174 char pubkey_hex[65];
175 char r_hex[65];
176 char s_hex[65];
177 char msg_hex[128];
178
179 for (int i = 0; i < 32; i++) {
180 safe_snprintf(pubkey_hex + i * 2, 3, "%02x", public_key[i]);
181 safe_snprintf(r_hex + i * 2, 3, "%02x", signature[i]);
182 safe_snprintf(s_hex + i * 2, 3, "%02x", signature[32 + i]);
183 }
184 for (size_t i = 0; i < (message_len < 32 ? message_len : 32); i++) {
185 safe_snprintf(msg_hex + i * 2, 3, "%02x", message[i]);
186 }
187
188 log_debug("gpg_verify_signature: pubkey=%s", pubkey_hex);
189 log_debug("gpg_verify_signature: R=%s", r_hex);
190 log_debug("gpg_verify_signature: S=%s", s_hex);
191 log_debug("gpg_verify_signature: msg=%s (len=%zu)", msg_hex, message_len);
192
193 // Verify the signature
194 err = gcry_pk_verify(s_sig, s_data, s_pubkey);
195
196 // Clean up S-expressions
197 gcry_sexp_release(s_pubkey);
198 gcry_sexp_release(s_sig);
199 gcry_sexp_release(s_data);
200
201 if (err) {
202 log_debug("gpg_verify_signature: Signature verification failed: %s", gcry_strerror(err));
203 return -1;
204 }
205
206 log_debug("gpg_verify_signature: Signature verified successfully");
207 return 0;
208#else
209 // Explicitly mark parameters as unused when libgcrypt is not available
210 (void)public_key;
211 (void)message;
212 (void)message_len;
213 (void)signature;
214 log_error("gpg_verify_signature: libgcrypt not available");
215 return -1;
216#endif
217}
218
219int gpg_verify_signature_with_binary(const uint8_t *signature, size_t signature_len, const uint8_t *message,
220 size_t message_len, const char *expected_key_id) {
221 // Validate inputs
222 if (!signature || signature_len == 0 || signature_len > 512) {
223 log_error("gpg_verify_signature_with_binary: Invalid signature (expected 1-512 bytes, got %zu)", signature_len);
224 return -1;
225 }
226 if (!message || message_len == 0) {
227 log_error("gpg_verify_signature_with_binary: Invalid message");
228 return -1;
229 }
230
231 // Create temporary files for signature and message
232 char sig_path[PLATFORM_MAX_PATH_LENGTH];
233 char msg_path[PLATFORM_MAX_PATH_LENGTH];
234 int sig_fd = -1;
235 int msg_fd = -1;
236 int result = -1;
237
238 // Create temp files using platform abstraction
239 if (platform_create_temp_file(sig_path, sizeof(sig_path), "asciichat_sig", &sig_fd) != 0) {
240 log_error("Failed to create signature temp file");
241 return -1;
242 }
243
244 if (platform_create_temp_file(msg_path, sizeof(msg_path), "asciichat_msg", &msg_fd) != 0) {
245 log_error("Failed to create message temp file");
247 return -1;
248 }
249
250#ifdef _WIN32
251 // Windows: Write to already-created files using CreateFileA
252 HANDLE sig_handle = CreateFileA(sig_path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, NULL);
253 if (sig_handle == INVALID_HANDLE_VALUE) {
254 log_error("Failed to open signature temp file: %lu", GetLastError());
257 return -1;
258 }
259
260 DWORD bytes_written;
261 if (!WriteFile(sig_handle, signature, (DWORD)signature_len, &bytes_written, NULL) || bytes_written != signature_len) {
262 log_error("Failed to write signature to temp file: %lu", GetLastError());
263 CloseHandle(sig_handle);
266 return -1;
267 }
268 CloseHandle(sig_handle);
269
270 HANDLE msg_handle = CreateFileA(msg_path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, NULL);
271 if (msg_handle == INVALID_HANDLE_VALUE) {
272 log_error("Failed to open message temp file: %lu", GetLastError());
275 return -1;
276 }
277
278 if (!WriteFile(msg_handle, message, (DWORD)message_len, &bytes_written, NULL) || bytes_written != message_len) {
279 log_error("Failed to write message to temp file: %lu", GetLastError());
280 CloseHandle(msg_handle);
283 return -1;
284 }
285 CloseHandle(msg_handle);
286
287#else
288 // Unix: Write to open file descriptors returned by platform_create_temp_file
289 ssize_t sig_written = write(sig_fd, signature, signature_len);
290 if (sig_written != (ssize_t)signature_len) {
291 log_error("Failed to write signature to temp file: %s", SAFE_STRERROR(errno));
292 close(sig_fd);
293 close(msg_fd);
296 return -1;
297 }
298 close(sig_fd);
299
300 ssize_t msg_written = write(msg_fd, message, message_len);
301 if (msg_written != (ssize_t)message_len) {
302 log_error("Failed to write message to temp file: %s", SAFE_STRERROR(errno));
303 close(msg_fd);
306 return -1;
307 }
308 close(msg_fd);
309#endif
310
311 // Build gpg --verify command
312 char cmd[BUFFER_SIZE_LARGE];
313#ifdef _WIN32
314 safe_snprintf(cmd, sizeof(cmd), "gpg --verify \"%s\" \"%s\" 2>&1", sig_path, msg_path);
315#else
316 safe_snprintf(cmd, sizeof(cmd), "gpg --verify '%s' '%s' 2>&1", sig_path, msg_path);
317#endif
318
319 log_debug("Running GPG verify command: %s", cmd);
320
321 // Execute gpg --verify command
322 FILE *fp;
323 platform_popen("gpg_verify", cmd, "r", &fp);
324 if (!fp) {
325 log_error("Failed to execute gpg --verify command");
326 goto cleanup;
327 }
328
329 // Parse output for "Good signature" and verify key ID
330 char line[BUFFER_SIZE_MEDIUM];
331 bool found_good_sig = false;
332 bool found_key_id = false;
333
334 while (fgets(line, sizeof(line), fp)) {
335 log_debug("GPG output: %s", line);
336
337 // Check for "Good signature"
338 if (strstr(line, "Good signature")) {
339 found_good_sig = true;
340 }
341
342 // Check if this line contains the expected key ID (GPG outputs key ID on separate line)
343 if (expected_key_id && strlen(expected_key_id) > 0) {
344 if (strstr(line, expected_key_id)) {
345 found_key_id = true;
346 log_debug("Found expected key ID in GPG output: %s", expected_key_id);
347 }
348 }
349
350 // Check for signature errors
351 if (strstr(line, "BAD signature")) {
352 log_error("GPG reports BAD signature");
353 platform_pclose(&fp);
354 fp = NULL;
355 goto cleanup;
356 }
357 }
358
359 // Check exit code
360 int status = platform_pclose(&fp);
361 fp = NULL;
362
363#ifdef _WIN32
364 int exit_code = status;
365#else
366 int exit_code = WEXITSTATUS(status);
367#endif
368
369 if (exit_code != 0) {
370 log_error("GPG verify failed with exit code: %d", exit_code);
371 goto cleanup;
372 }
373
374 if (!found_good_sig) {
375 log_error("GPG verify did not report 'Good signature'");
376 goto cleanup;
377 }
378
379 // If expected_key_id was provided, verify we found it in the output
380 if (expected_key_id && strlen(expected_key_id) > 0) {
381 if (!found_key_id) {
382 log_error("GPG signature key ID does not match expected key ID: %s", expected_key_id);
383 goto cleanup;
384 }
385 }
386
387 log_debug("GPG signature verified successfully via gpg --verify binary");
388 result = 0;
389
390cleanup:
391 // Clean up temp files
394
395 if (fp) {
396 platform_pclose(&fp);
397 }
398
399 return result;
400}
⚙️ Common definitions, error codes, macros, and types shared throughout the application
Cross-platform error handling utilities.
Cross-platform filesystem operations.
#define BUFFER_SIZE_LARGE
Large buffer size (1024 bytes)
#define BUFFER_SIZE_MEDIUM
Medium buffer size (512 bytes)
#define SAFE_STRERROR(errnum)
Definition common.h:465
unsigned char uint8_t
Definition common.h:56
int gpg_verify_signature(const uint8_t *public_key, const uint8_t *message, size_t message_len, const uint8_t *signature)
Verify Ed25519 signature using libgcrypt (no GPG binary required)
int gpg_verify_signature_with_binary(const uint8_t *signature, size_t signature_len, const uint8_t *message, size_t message_len, const char *expected_key_id)
Verify OpenPGP signature using GPG binary.
int gpg_verify_detached_ed25519(const char *key_id, const uint8_t *message, size_t message_len, const uint8_t signature[64])
Verify Ed25519 signature using GPG binary.
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:41
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
asciichat_error_t platform_popen(const char *name, const char *command, const char *mode, FILE **out_stream)
Execute a command and return a file stream for reading/writing.
int platform_create_temp_file(char *path_out, size_t path_size, const char *prefix, int *fd)
Create a temporary file with a given prefix.
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:148
int platform_unlink(const char *pathname)
Delete/unlink file.
asciichat_error_t platform_pclose(FILE **stream_ptr)
Close a process stream opened with platform_popen()
int errno
int platform_delete_temp_file(const char *path)
Delete a temporary file.
📝 Logging API with multiple log levels and terminal output control
atomic_t bytes_written
Definition mmap.c:42
Cross-platform process types and execution utilities.
GPG message signing interface.
#define PLATFORM_MAX_PATH_LENGTH
Definition system.c:69
Cross-platform system functions interface for ascii-chat.
🔤 String Manipulation and Shell Escaping Utilities
Common validation macros to reduce duplication in protocol handlers.
GPG signature verification interface.