7#include <ascii-chat/crypto/gpg/gpg_keys.h>
8#include <ascii-chat/crypto/gpg/openpgp.h>
9#include <ascii-chat/crypto/keys_validation.h>
10#include <ascii-chat/common.h>
11#include <ascii-chat/asciichat_errno.h>
12#include <ascii-chat/platform/string.h>
13#include <ascii-chat/platform/process.h>
14#include <ascii-chat/crypto/gpg/export.h>
25asciichat_error_t
parse_gpg_key(
const char *gpg_key_id, public_key_t *key_out) {
26 if (!gpg_key_id || !key_out) {
27 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters: gpg_key_id=%p, key_out=%p", gpg_key_id, key_out);
28 return ERROR_INVALID_PARAM;
35 const char *key_id = gpg_key_id;
36 if (strncmp(key_id,
"0x", 2) == 0 || strncmp(key_id,
"0X", 2) == 0) {
40 size_t key_id_len = strlen(key_id);
41 if (key_id_len != 8 && key_id_len != 16 && key_id_len != 40) {
42 return SET_ERRNO(ERROR_CRYPTO_KEY,
"Invalid GPG key ID length: %zu (expected 8, 16, or 40 hex chars)", key_id_len);
46 for (
size_t i = 0; i < key_id_len; i++) {
48 if (!((c >=
'0' && c <=
'9') || (c >=
'a' && c <=
'f') || (c >=
'A' && c <=
'F'))) {
49 return SET_ERRNO(ERROR_CRYPTO_KEY,
"Invalid GPG key ID: contains non-hex character '%c'", c);
54 uint8_t ed25519_pk[32];
56 if (extract_result != ASCIICHAT_OK) {
57 return extract_result;
61 memset(key_out, 0,
sizeof(public_key_t));
62 key_out->type = KEY_TYPE_GPG;
63 memcpy(key_out->key, ed25519_pk, 32);
66 safe_snprintf(key_out->comment,
sizeof(key_out->comment),
"GPG key %s", key_id);
71asciichat_error_t
parse_gpg_key_binary(
const uint8_t *gpg_key_binary,
size_t key_size, public_key_t *key_out) {
72 if (!gpg_key_binary || !key_out) {
73 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters: gpg_key_binary=%p, key_out=%p", gpg_key_binary, key_out);
74 return ERROR_INVALID_PARAM;
78 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid key size: %zu", key_size);
79 return ERROR_INVALID_PARAM;
83 char *armored_text = SAFE_MALLOC(key_size + 1,
char *);
84 memcpy(armored_text, gpg_key_binary, key_size);
85 armored_text[key_size] =
'\0';
88 uint8_t ed25519_pk[32];
90 SAFE_FREE(armored_text);
92 if (result != ASCIICHAT_OK) {
97 memset(key_out, 0,
sizeof(public_key_t));
98 key_out->type = KEY_TYPE_GPG;
99 memcpy(key_out->key, ed25519_pk, 32);
102 safe_snprintf(key_out->comment,
sizeof(key_out->comment),
"GPG Ed25519 key");
108 if (!gpg_key_id || !ed25519_pk) {
109 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters: gpg_key_id=%p, ed25519_pk=%p", gpg_key_id, ed25519_pk);
110 return ERROR_INVALID_PARAM;
117 return SET_ERRNO(ERROR_CRYPTO_KEY,
"Failed to extract Ed25519 public key from GPG for key ID: %s", gpg_key_id);
124 if (!gpg_key_text || !x25519_pk) {
125 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters: gpg_key_text=%p, x25519_pk=%p", gpg_key_text, x25519_pk);
126 return ERROR_INVALID_PARAM;
130 uint8_t ed25519_pk[32];
132 if (extract_result != ASCIICHAT_OK) {
133 return extract_result;
137 if (crypto_sign_ed25519_pk_to_curve25519(x25519_pk, ed25519_pk) != 0) {
138 SET_ERRNO(ERROR_CRYPTO_KEY,
"Failed to convert Ed25519 to X25519");
139 return ERROR_CRYPTO_KEY;
150 if (!gpg_key_text || !fingerprint_out) {
151 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters: gpg_key_text=%p, fingerprint_out=%p", gpg_key_text,
153 return ERROR_INVALID_PARAM;
159 SET_ERRNO(ERROR_CRYPTO_KEY,
"GPG fingerprint extraction not yet implemented");
160 return ERROR_CRYPTO_KEY;
163asciichat_error_t
get_gpg_key_id(
const char *gpg_key_text, uint8_t key_id_out[8]) {
164 if (!gpg_key_text || !key_id_out) {
165 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters: gpg_key_text=%p, key_id_out=%p", gpg_key_text, key_id_out);
166 return ERROR_INVALID_PARAM;
172 SET_ERRNO(ERROR_CRYPTO_KEY,
"GPG key ID extraction not yet implemented");
173 return ERROR_CRYPTO_KEY;
177 if (!gpg_key_text || !is_expired) {
178 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters: gpg_key_text=%p, is_expired=%p", gpg_key_text, is_expired);
179 return ERROR_INVALID_PARAM;
185 size_t key_len = strlen(gpg_key_text);
186 const char *key_id = gpg_key_text;
190 for (
size_t i = 0; i < key_len; i++) {
191 if (!((gpg_key_text[i] >=
'0' && gpg_key_text[i] <=
'9') || (gpg_key_text[i] >=
'A' && gpg_key_text[i] <=
'F') ||
192 (gpg_key_text[i] >=
'a' && gpg_key_text[i] <=
'f'))) {
199 if (!is_hex || (key_len != 8 && key_len != 16 && key_len != 40)) {
200 log_warn(
"check_gpg_key_expiry: Input is not a key ID format (expected 8/16/40 hex chars)");
206 char cmd[BUFFER_SIZE_MEDIUM];
207 safe_snprintf(cmd,
sizeof(cmd),
"gpg --list-keys --with-colons %s " PLATFORM_SHELL_NULL_REDIRECT, key_id);
210 if (platform_popen(cmd,
"r", &fp) != ASCIICHAT_OK || !fp) {
211 log_error(
"Failed to run gpg --list-keys for key %s", key_id);
216 char line[BUFFER_SIZE_LARGE];
217 bool found_pub =
false;
223 while (fgets(line,
sizeof(line), fp)) {
224 if (strncmp(line,
"pub:", 4) == 0) {
231 char *field_start = ptr;
233 while (*ptr && field_count < 12) {
234 if (*ptr ==
':' || *ptr ==
'\n') {
236 fields[field_count++] = field_start;
237 field_start = ptr + 1;
243 if (field_count >= 7 && strlen(fields[6]) > 0) {
245 long expiry_timestamp = strtol(fields[6], NULL, 10);
246 time_t now = time(NULL);
248 if (expiry_timestamp > 0 && expiry_timestamp < now) {
250 log_warn(
"GPG key %s has expired (expiry: %ld, now: %ld)", key_id, expiry_timestamp, (
long)now);
251 }
else if (expiry_timestamp > 0) {
252 log_debug(
"GPG key %s expires at timestamp %ld (valid)", key_id, expiry_timestamp);
254 log_debug(
"GPG key %s has no expiration date", key_id);
257 log_debug(
"GPG key %s has no expiration date (field empty)", key_id);
264 platform_pclose(&fp);
267 log_warn(
"Could not find GPG key %s in keyring", key_id);
279 if (!gpg_key_text || !output) {
280 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters: gpg_key_text=%p, output=%p", gpg_key_text, output);
281 return ERROR_INVALID_PARAM;
284 if (output_size < 64) {
285 SET_ERRNO(ERROR_INVALID_PARAM,
"Output buffer too small: %zu (minimum 64)", output_size);
286 return ERROR_INVALID_PARAM;
291 asciichat_error_t key_id_result =
get_gpg_key_id(gpg_key_text, key_id);
292 if (key_id_result != ASCIICHAT_OK) {
294 SAFE_STRNCPY(output,
"GPG key (key ID extraction failed)", output_size - 1);
300 for (
int i = 0; i < 8; i++) {
303 hex_key_id[16] =
'\0';
306 int result =
safe_snprintf(output, output_size,
"GPG key ID: %s", hex_key_id);
307 if (result < 0 || result >= (
int)output_size) {
308 SET_ERRNO(ERROR_STRING,
"Failed to format GPG key display");
316 if (!gpg_key_text || !comment_out) {
317 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters: gpg_key_text=%p, comment_out=%p", gpg_key_text, comment_out);
318 return ERROR_INVALID_PARAM;
321 if (comment_size == 0) {
322 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid comment size: %zu", comment_size);
323 return ERROR_INVALID_PARAM;
330 SAFE_STRNCPY(comment_out,
"GPG key", comment_size - 1);
int gpg_get_public_key(const char *key_id, uint8_t *public_key_out, char *keygrip_out)
asciichat_error_t check_gpg_key_expiry(const char *gpg_key_text, bool *is_expired)
asciichat_error_t extract_gpg_key_comment(const char *gpg_key_text, char *comment_out, size_t comment_size)
asciichat_error_t get_gpg_key_id(const char *gpg_key_text, uint8_t key_id_out[8])
asciichat_error_t get_gpg_fingerprint(const char *gpg_key_text, uint8_t fingerprint_out[20])
asciichat_error_t parse_gpg_key(const char *gpg_key_id, public_key_t *key_out)
asciichat_error_t parse_gpg_key_binary(const uint8_t *gpg_key_binary, size_t key_size, public_key_t *key_out)
asciichat_error_t format_gpg_key_display(const char *gpg_key_text, char *output, size_t output_size)
asciichat_error_t gpg_to_x25519_public(const char *gpg_key_text, uint8_t x25519_pk[32])
asciichat_error_t extract_ed25519_from_gpg(const char *gpg_key_id, uint8_t ed25519_pk[32])
asciichat_error_t openpgp_parse_armored_pubkey(const char *armored_text, uint8_t ed25519_pk[32])
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.