ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
signing.c File Reference

GPG signing operations implementation. More...

Go to the source code of this file.

Functions

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)
 
int gpg_sign_detached_ed25519 (const char *key_id, const uint8_t *message, size_t message_len, uint8_t signature_out[64])
 

Detailed Description

GPG signing operations implementation.

Definition in file signing.c.

Function Documentation

◆ gpg_sign_detached_ed25519()

int gpg_sign_detached_ed25519 ( const char *  key_id,
const uint8_t *  message,
size_t  message_len,
uint8_t  signature_out[64] 
)

Definition at line 140 of file signing.c.

141 {
142 log_debug("gpg_sign_detached_ed25519: Signing with key ID %s (fallback mode)", key_id);
143
144 // Get OpenPGP signature packet from gpg --detach-sign
145 uint8_t openpgp_signature[512];
146 size_t openpgp_len = 0;
147
148 int result = gpg_sign_with_key(key_id, message, message_len, openpgp_signature, &openpgp_len);
149 if (result != 0) {
150 log_error("GPG detached signing failed for key %s", key_id);
151 return -1;
152 }
153
154 log_debug("gpg_sign_with_key returned %zu bytes", openpgp_len);
155
156 if (openpgp_len < 10) {
157 log_error("GPG signature too short: %zu bytes", openpgp_len);
158 return -1;
159 }
160
161 log_debug("Parsing OpenPGP signature packet (%zu bytes) to extract Ed25519 signature", openpgp_len);
162
163 // Parse OpenPGP signature packet format
164 // Reference: RFC 4880 Section 5.2 (Signature Packet)
165 // Format: [header][version][type][algo][hash-algo][...][signature-data]
166 size_t offset = 0;
167
168 // Parse packet header
169 uint8_t tag = openpgp_signature[offset++];
170 size_t packet_len = 0;
171
172 if ((tag & 0x40) == 0) {
173 // Old format packet
174 uint8_t length_type = tag & 0x03;
175 if (length_type == 0) {
176 packet_len = openpgp_signature[offset++];
177 } else if (length_type == 1) {
178 packet_len = (openpgp_signature[offset] << 8) | openpgp_signature[offset + 1];
179 offset += 2;
180 } else if (length_type == 2) {
181 packet_len = (openpgp_signature[offset] << 24) | (openpgp_signature[offset + 1] << 16) |
182 (openpgp_signature[offset + 2] << 8) | openpgp_signature[offset + 3];
183 offset += 4;
184 } else {
185 log_error("Unsupported old-format packet length type: %d", length_type);
186 return -1;
187 }
188 } else {
189 // New format packet
190 uint8_t length_byte = openpgp_signature[offset++];
191 if (length_byte < 192) {
192 packet_len = length_byte;
193 } else if (length_byte < 224) {
194 packet_len = ((length_byte - 192) << 8) + openpgp_signature[offset++] + 192;
195 } else if (length_byte == 255) {
196 packet_len = (openpgp_signature[offset] << 24) | (openpgp_signature[offset + 1] << 16) |
197 (openpgp_signature[offset + 2] << 8) | openpgp_signature[offset + 3];
198 offset += 4;
199 } else {
200 log_error("Unsupported new-format packet length encoding: %d", length_byte);
201 return -1;
202 }
203 }
204
205 if (offset + packet_len > openpgp_len) {
206 log_error("Packet length exceeds signature size: %zu + %zu > %zu", offset, packet_len, openpgp_len);
207 return -1;
208 }
209
210 log_debug("Signature packet: offset=%zu, length=%zu", offset, packet_len);
211
212 // Parse signature packet body
213 // Skip: version (1), sig_type (1), pub_algo (1), hash_algo (1)
214 if (offset + 4 > openpgp_len) {
215 log_error("Signature packet too short for header");
216 return -1;
217 }
218
219 uint8_t version = openpgp_signature[offset++];
220 uint8_t sig_type = openpgp_signature[offset++];
221 uint8_t pub_algo = openpgp_signature[offset++];
222 uint8_t hash_algo = openpgp_signature[offset++];
223
224 log_debug("Signature: version=%d, type=%d, algo=%d, hash=%d", version, sig_type, pub_algo, hash_algo);
225
226 // Verify algorithm is Ed25519 (22 = EdDSA)
227 if (pub_algo != 22) {
228 log_error("Expected EdDSA algorithm (22), got %d", pub_algo);
229 return -1;
230 }
231
232 // For v4 signatures: skip hashed subpackets
233 if (version == 4) {
234 if (offset + 2 > openpgp_len) {
235 log_error("Cannot read hashed subpacket length");
236 return -1;
237 }
238 uint16_t hashed_len = (openpgp_signature[offset] << 8) | openpgp_signature[offset + 1];
239 offset += 2;
240 offset += hashed_len; // Skip hashed subpackets
241
242 if (offset + 2 > openpgp_len) {
243 log_error("Cannot read unhashed subpacket length");
244 return -1;
245 }
246 uint16_t unhashed_len = (openpgp_signature[offset] << 8) | openpgp_signature[offset + 1];
247 offset += 2;
248 offset += unhashed_len; // Skip unhashed subpackets
249
250 // Skip left 16 bits of signed hash value
251 if (offset + 2 > openpgp_len) {
252 log_error("Cannot read hash left bits");
253 return -1;
254 }
255 offset += 2;
256 }
257
258 // Now we're at the signature data (MPI format for Ed25519)
259 // Ed25519 signature is: r (32 bytes) || s (32 bytes) = 64 bytes total
260 // In OpenPGP, each MPI is encoded as: [2-byte bit count][data]
261
262 if (offset + 2 > openpgp_len) {
263 log_error("Cannot read MPI bit count for R");
264 return -1;
265 }
266
267 uint16_t r_bits = (openpgp_signature[offset] << 8) | openpgp_signature[offset + 1];
268 offset += 2;
269 size_t r_bytes = (r_bits + 7) / 8;
270
271 log_debug("R: %d bits (%zu bytes)", r_bits, r_bytes);
272
273 if (r_bytes != 32) {
274 log_error("Expected 32-byte R value, got %zu bytes", r_bytes);
275 return -1;
276 }
277
278 if (offset + r_bytes > openpgp_len) {
279 log_error("R value exceeds packet size");
280 return -1;
281 }
282
283 memcpy(signature_out, &openpgp_signature[offset], 32);
284 offset += r_bytes;
285
286 // Read S value
287 if (offset + 2 > openpgp_len) {
288 log_error("Cannot read MPI bit count for S");
289 return -1;
290 }
291
292 uint16_t s_bits = (openpgp_signature[offset] << 8) | openpgp_signature[offset + 1];
293 offset += 2;
294 size_t s_bytes = (s_bits + 7) / 8;
295
296 log_debug("S: %d bits (%zu bytes)", s_bits, s_bytes);
297
298 if (s_bytes != 32) {
299 log_error("Expected 32-byte S value, got %zu bytes", s_bytes);
300 return -1;
301 }
302
303 if (offset + s_bytes > openpgp_len) {
304 log_error("S value exceeds packet size");
305 return -1;
306 }
307
308 memcpy(signature_out + 32, &openpgp_signature[offset], 32);
309
310 log_debug("Successfully extracted 64-byte Ed25519 signature from OpenPGP packet");
311
312 // Debug: Log signature components
313 char hex_r[65], hex_s[65];
314 for (int i = 0; i < 32; i++) {
315 safe_snprintf(hex_r + i * 2, 3, "%02x", signature_out[i]);
316 safe_snprintf(hex_s + i * 2, 3, "%02x", signature_out[i + 32]);
317 }
318 hex_r[64] = hex_s[64] = '\0';
319 log_debug("Signature R (first 32 bytes): %s", hex_r);
320 log_debug("Signature S (last 32 bytes): %s", hex_s);
321
322 return 0;
323}
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
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:456

References gpg_sign_with_key(), and safe_snprintf().

Referenced by ed25519_sign_message().

◆ gpg_sign_with_key()

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)

This function uses gpg --detach-sign which internally uses gpg-agent, so no passphrase prompt if the key is cached in the agent.

Parameters
key_idGPG key ID (e.g., "7FE90A79F2E80ED3")
messageMessage to sign
message_lenMessage length
signature_outOutput buffer for signature (caller must provide at least 512 bytes)
signature_len_outActual signature length written
Returns
0 on success, -1 on error

Definition at line 41 of file signing.c.

42 {
43 if (!key_id || !message || message_len == 0 || !signature_out || !signature_len_out) {
44 log_error("Invalid parameters to gpg_sign_with_key");
45 return -1;
46 }
47
48 char msg_path[PLATFORM_MAX_PATH_LENGTH];
49 char sig_path[PLATFORM_MAX_PATH_LENGTH];
50 int msg_fd = -1;
51 int sig_fd = -1;
52 int result = -1;
53
54 // Create temp files using platform abstraction
55 if (platform_create_temp_file(msg_path, sizeof(msg_path), "asciichat_msg", &msg_fd) != 0) {
56 log_error("Failed to create temp message file");
57 return -1;
58 }
59
60 if (platform_create_temp_file(sig_path, sizeof(sig_path), "asciichat_sig", &sig_fd) != 0) {
61 log_error("Failed to create temp signature file");
62 platform_delete_temp_file(msg_path);
63 return -1;
64 }
65
66 // Close signature file descriptor (will be created by gpg)
67 if (sig_fd >= 0) {
68 close(sig_fd);
69 }
70 platform_delete_temp_file(sig_path); // Remove it so gpg can create it fresh
71
72 // Write message to temp file
73 ssize_t written = write(msg_fd, message, message_len);
74 close(msg_fd);
75 msg_fd = -1;
76
77 if (written != (ssize_t)message_len) {
78 log_error("Failed to write message to temp file");
79 goto cleanup;
80 }
81
82 // Escape key ID for shell command (prevent injection)
83 char escaped_key_id[64];
84 if (!escape_path_for_shell(key_id, escaped_key_id, sizeof(escaped_key_id))) {
85 log_error("Failed to escape GPG key ID for shell command");
86 goto cleanup;
87 }
88
89 // Call gpg --detach-sign
90 char cmd[BUFFER_SIZE_LARGE];
91 safe_snprintf(cmd, sizeof(cmd),
92 "gpg --local-user 0x%s --detach-sign --output \"%s\" \"%s\" " PLATFORM_SHELL_NULL_REDIRECT,
93 escaped_key_id, sig_path, msg_path);
94
95 log_debug("Signing with GPG: %s", cmd);
96 int status = system(cmd);
97 if (status != 0) {
98 log_error("GPG signing failed (exit code %d)", status);
99 goto cleanup;
100 }
101
102 // Read signature file
103 FILE *sig_fp = platform_fopen(sig_path, "rb");
104 if (!sig_fp) {
105 log_error("Failed to open signature file: %s", SAFE_STRERROR(errno));
106 goto cleanup;
107 }
108
109 fseek(sig_fp, 0, SEEK_END);
110 long sig_size = ftell(sig_fp);
111 fseek(sig_fp, 0, SEEK_SET);
112
113 if (sig_size <= 0 || sig_size > 512) {
114 log_error("Invalid signature size: %ld bytes", sig_size);
115 fclose(sig_fp);
116 goto cleanup;
117 }
118
119 size_t bytes_read = fread(signature_out, 1, sig_size, sig_fp);
120 fclose(sig_fp);
121
122 if (bytes_read != (size_t)sig_size) {
123 log_error("Failed to read signature file");
124 goto cleanup;
125 }
126
127 *signature_len_out = sig_size;
128 log_debug("GPG signature created successfully (%zu bytes)", *signature_len_out);
129 result = 0;
130
131cleanup:
132 if (msg_fd >= 0) {
133 close(msg_fd);
134 }
135 platform_delete_temp_file(msg_path);
136 platform_delete_temp_file(sig_path);
137 return result;
138}
#define PLATFORM_MAX_PATH_LENGTH
Definition system.c:64
bool escape_path_for_shell(const char *path, char *out_buffer, size_t out_buffer_size)
FILE * platform_fopen(const char *filename, const char *mode)

References escape_path_for_shell(), platform_fopen(), PLATFORM_MAX_PATH_LENGTH, and safe_snprintf().

Referenced by gpg_sign_detached_ed25519(), and gpg_verify_detached_ed25519().