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

Discovery Server Public Key Trust Management Implementation. More...

Go to the source code of this file.

Functions

asciichat_error_t discovery_keys_download_https (const char *url, uint8_t pubkey_out[32])
 
asciichat_error_t discovery_keys_load_file (const char *file_path, uint8_t pubkey_out[32])
 
asciichat_error_t discovery_keys_fetch_github (const char *username, bool is_gpg, uint8_t pubkey_out[32])
 
asciichat_error_t discovery_keys_fetch_gitlab (const char *username, uint8_t pubkey_out[32])
 
asciichat_error_t discovery_keys_get_cache_path (const char *acds_server, char *path_out, size_t path_size)
 
asciichat_error_t discovery_keys_load_cached (const char *acds_server, uint8_t pubkey_out[32])
 
asciichat_error_t discovery_keys_save_cached (const char *acds_server, const uint8_t pubkey[32])
 
asciichat_error_t discovery_keys_clear_cache (const char *acds_server)
 
asciichat_error_t discovery_keys_verify_change (const char *acds_server, const uint8_t old_pubkey[32], const uint8_t new_pubkey[32])
 
asciichat_error_t discovery_keys_verify (const char *acds_server, const char *key_spec, uint8_t pubkey_out[32])
 

Detailed Description

Discovery Server Public Key Trust Management Implementation.

Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
January 2026

Definition in file discovery_keys.c.

Function Documentation

◆ discovery_keys_clear_cache()

asciichat_error_t discovery_keys_clear_cache ( const char *  acds_server)

Definition at line 237 of file discovery_keys.c.

237 {
238 if (!acds_server) {
239 return SET_ERRNO(ERROR_INVALID_PARAM, "NULL parameter in discovery_keys_clear_cache");
240 }
241
242 char cache_path[PLATFORM_MAX_PATH_LENGTH];
243 asciichat_error_t result = discovery_keys_get_cache_path(acds_server, cache_path, sizeof(cache_path));
244 if (result != ASCIICHAT_OK) {
245 return result;
246 }
247
248 if (platform_is_regular_file(cache_path)) {
249 if (remove(cache_path) != 0) {
250 return SET_ERRNO_SYS(ERROR_FILE_OPERATION, "Failed to delete cached key: %s", cache_path);
251 }
252 log_debug("Cleared cached ACDS key for server: %s", acds_server);
253 }
254
255 return ASCIICHAT_OK;
256}
asciichat_error_t discovery_keys_get_cache_path(const char *acds_server, char *path_out, size_t path_size)
#define PLATFORM_MAX_PATH_LENGTH
Definition system.c:64
int platform_is_regular_file(const char *path)
Definition util.c:122

References discovery_keys_get_cache_path(), platform_is_regular_file(), and PLATFORM_MAX_PATH_LENGTH.

◆ discovery_keys_download_https()

asciichat_error_t discovery_keys_download_https ( const char *  url,
uint8_t  pubkey_out[32] 
)

Definition at line 59 of file discovery_keys.c.

59 {
60 if (!url || !pubkey_out) {
61 return SET_ERRNO(ERROR_INVALID_PARAM, "NULL parameter in discovery_keys_download_https");
62 }
63
64 log_debug("Downloading ACDS key from %s", url);
65
66 // Use existing parse_public_key infrastructure which handles HTTPS URLs
67 public_key_t key;
68 asciichat_error_t result = parse_public_key(url, &key);
69 if (result != ASCIICHAT_OK) {
70 return SET_ERRNO(ERROR_CRYPTO_KEY, "Failed to download and parse ACDS key from %s", url);
71 }
72
73 memcpy(pubkey_out, key.key, 32);
74 log_debug("Successfully downloaded and parsed ACDS key from %s", url);
75 return ASCIICHAT_OK;
76}
asciichat_error_t parse_public_key(const char *input, public_key_t *key_out)
Definition keys.c:29

References parse_public_key().

◆ discovery_keys_fetch_github()

asciichat_error_t discovery_keys_fetch_github ( const char *  username,
bool  is_gpg,
uint8_t  pubkey_out[32] 
)

Definition at line 101 of file discovery_keys.c.

101 {
102 if (!username || !pubkey_out) {
103 return SET_ERRNO(ERROR_INVALID_PARAM, "NULL parameter in discovery_keys_fetch_github");
104 }
105
106 log_debug("Fetching ACDS key from GitHub for user: %s", username);
107
108 // Use existing parse_public_key infrastructure
109 char key_spec[BUFFER_SIZE_MEDIUM];
110 if (is_gpg) {
111 safe_snprintf(key_spec, sizeof(key_spec), "github:%s.gpg", username);
112 } else {
113 safe_snprintf(key_spec, sizeof(key_spec), "github:%s", username);
114 }
115
116 public_key_t key;
117 asciichat_error_t result = parse_public_key(key_spec, &key);
118 if (result != ASCIICHAT_OK) {
119 return SET_ERRNO(ERROR_CRYPTO_KEY, "Failed to fetch ACDS key from GitHub: %s", username);
120 }
121
122 memcpy(pubkey_out, key.key, 32);
123 return ASCIICHAT_OK;
124}
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:456

References parse_public_key(), and safe_snprintf().

◆ discovery_keys_fetch_gitlab()

asciichat_error_t discovery_keys_fetch_gitlab ( const char *  username,
uint8_t  pubkey_out[32] 
)

Definition at line 126 of file discovery_keys.c.

126 {
127 if (!username || !pubkey_out) {
128 return SET_ERRNO(ERROR_INVALID_PARAM, "NULL parameter in discovery_keys_fetch_gitlab");
129 }
130
131 log_debug("Fetching ACDS key from GitLab for user: %s", username);
132
133 // Use existing parse_public_key infrastructure
134 char key_spec[BUFFER_SIZE_MEDIUM];
135 safe_snprintf(key_spec, sizeof(key_spec), "gitlab:%s.gpg", username);
136
137 public_key_t key;
138 asciichat_error_t result = parse_public_key(key_spec, &key);
139 if (result != ASCIICHAT_OK) {
140 return SET_ERRNO(ERROR_CRYPTO_KEY, "Failed to fetch ACDS key from GitLab: %s", username);
141 }
142
143 memcpy(pubkey_out, key.key, 32);
144 return ASCIICHAT_OK;
145}

References parse_public_key(), and safe_snprintf().

◆ discovery_keys_get_cache_path()

asciichat_error_t discovery_keys_get_cache_path ( const char *  acds_server,
char *  path_out,
size_t  path_size 
)

Definition at line 151 of file discovery_keys.c.

151 {
152 if (!acds_server || !path_out || path_size == 0) {
153 return SET_ERRNO(ERROR_INVALID_PARAM, "NULL parameter in discovery_keys_get_cache_path");
154 }
155
156 char *config_dir = get_config_dir();
157 if (!config_dir) {
158 return SET_ERRNO(ERROR_CONFIG, "Failed to get config directory");
159 }
160
161 // Path: ~/.config/ascii-chat/acds_keys/<hostname>/key.pub
162 safe_snprintf(path_out, path_size, "%s" ACDS_KEYS_CACHE_DIR PATH_SEPARATOR_STR "%s" PATH_SEPARATOR_STR "key.pub",
163 config_dir, acds_server);
164 SAFE_FREE(config_dir);
165
166 return ASCIICHAT_OK;
167}
char * get_config_dir(void)
Definition path.c:493

References get_config_dir(), and safe_snprintf().

Referenced by discovery_keys_clear_cache(), discovery_keys_load_cached(), and discovery_keys_save_cached().

◆ discovery_keys_load_cached()

asciichat_error_t discovery_keys_load_cached ( const char *  acds_server,
uint8_t  pubkey_out[32] 
)

Definition at line 169 of file discovery_keys.c.

169 {
170 if (!acds_server || !pubkey_out) {
171 return SET_ERRNO(ERROR_INVALID_PARAM, "NULL parameter in discovery_keys_load_cached");
172 }
173
174 char cache_path[PLATFORM_MAX_PATH_LENGTH];
175 asciichat_error_t result = discovery_keys_get_cache_path(acds_server, cache_path, sizeof(cache_path));
176 if (result != ASCIICHAT_OK) {
177 return result;
178 }
179
180 // Check if cached key exists
181 if (!platform_is_regular_file(cache_path)) {
182 return SET_ERRNO(ERROR_FILE_NOT_FOUND, "No cached key for ACDS server: %s", acds_server);
183 }
184
185 // Load cached key using existing infrastructure
186 return discovery_keys_load_file(cache_path, pubkey_out);
187}
asciichat_error_t discovery_keys_load_file(const char *file_path, uint8_t pubkey_out[32])

References discovery_keys_get_cache_path(), discovery_keys_load_file(), platform_is_regular_file(), and PLATFORM_MAX_PATH_LENGTH.

Referenced by discovery_keys_verify().

◆ discovery_keys_load_file()

asciichat_error_t discovery_keys_load_file ( const char *  file_path,
uint8_t  pubkey_out[32] 
)

Definition at line 78 of file discovery_keys.c.

78 {
79 if (!file_path || !pubkey_out) {
80 return SET_ERRNO(ERROR_INVALID_PARAM, "NULL parameter in discovery_keys_load_file");
81 }
82
83 log_debug("Loading ACDS key from file: %s", file_path);
84
85 // Use existing parse_public_key infrastructure which handles file paths
86 public_key_t key;
87 asciichat_error_t result = parse_public_key(file_path, &key);
88 if (result != ASCIICHAT_OK) {
89 return SET_ERRNO(ERROR_CRYPTO_KEY, "Failed to load ACDS key from file: %s", file_path);
90 }
91
92 memcpy(pubkey_out, key.key, 32);
93 log_debug("Successfully loaded ACDS key from file: %s", file_path);
94 return ASCIICHAT_OK;
95}
char file_path[PLATFORM_MAX_PATH_LENGTH]
Definition mmap.c:39

References file_path, and parse_public_key().

Referenced by discovery_keys_load_cached().

◆ discovery_keys_save_cached()

asciichat_error_t discovery_keys_save_cached ( const char *  acds_server,
const uint8_t  pubkey[32] 
)

Definition at line 189 of file discovery_keys.c.

189 {
190 if (!acds_server || !pubkey) {
191 return SET_ERRNO(ERROR_INVALID_PARAM, "NULL parameter in discovery_keys_save_cached");
192 }
193
194 char cache_path[PLATFORM_MAX_PATH_LENGTH];
195 asciichat_error_t result = discovery_keys_get_cache_path(acds_server, cache_path, sizeof(cache_path));
196 if (result != ASCIICHAT_OK) {
197 return result;
198 }
199
200 // Create cache directory if it doesn't exist
201 char dir_path[PLATFORM_MAX_PATH_LENGTH];
202 safe_snprintf(dir_path, sizeof(dir_path), "%s", cache_path);
203
204 // Find the last path separator (handle both / and \ for cross-platform compatibility)
205 char *last_sep = strrchr(dir_path, '\\');
206 if (!last_sep) {
207 last_sep = strrchr(dir_path, '/');
208 }
209
210 if (last_sep) {
211 *last_sep = '\0';
212 if (!platform_is_directory(dir_path)) {
213 asciichat_error_t result = platform_mkdir(dir_path, 0700);
214 if (result != ASCIICHAT_OK) {
215 return SET_ERRNO(ERROR_FILE_OPERATION, "Failed to create ACDS key cache directory: %s", dir_path);
216 }
217 }
218 }
219
220 // Save key in OpenSSH public key format
221 FILE *f = platform_fopen(cache_path, "w");
222 if (!f) {
223 return SET_ERRNO_SYS(ERROR_FILE_OPERATION, "Failed to create cache file: %s", cache_path);
224 }
225
226 // Convert binary Ed25519 key to base64 for OpenSSH format
227 char base64_key[128];
228 sodium_bin2base64(base64_key, sizeof(base64_key), pubkey, 32, sodium_base64_VARIANT_ORIGINAL);
229
230 fprintf(f, "ssh-ed25519 %s acds-cached-key\n", base64_key);
231 fclose(f);
232
233 log_debug("Cached ACDS key for server: %s", acds_server);
234 return ASCIICHAT_OK;
235}
FILE * platform_fopen(const char *filename, const char *mode)

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

Referenced by discovery_keys_verify().

◆ discovery_keys_verify()

asciichat_error_t discovery_keys_verify ( const char *  acds_server,
const char *  key_spec,
uint8_t  pubkey_out[32] 
)

Definition at line 304 of file discovery_keys.c.

304 {
305 if (!acds_server || !pubkey_out) {
306 return SET_ERRNO(ERROR_INVALID_PARAM, "NULL parameter in discovery_keys_verify");
307 }
308
309 uint8_t new_pubkey[32];
310 asciichat_error_t result;
311 bool is_official = is_official_server(acds_server);
312
313 // ===========================================================================
314 // Step 1: Obtain the public key
315 // ===========================================================================
316
317 if (!key_spec && is_official) {
318 // Automatic trust for official server: try SSH key first, then GPG
319 log_info("Attempting automatic HTTPS key trust for official ACDS server");
320
321 public_key_t key;
322 result = parse_public_key(ACDS_OFFICIAL_KEY_SSH_URL, &key);
323 if (result != ASCIICHAT_OK) {
324 log_debug("SSH key download failed, trying GPG key");
325 result = parse_public_key(ACDS_OFFICIAL_KEY_GPG_URL, &key);
326 }
327
328 if (result != ASCIICHAT_OK) {
329 return SET_ERRNO(ERROR_NETWORK, "Failed to download key from official ACDS server");
330 }
331
332 memcpy(new_pubkey, key.key, 32);
333
334 } else if (!key_spec) {
335 // No key_spec and not official server = error
336 return SET_ERRNO(ERROR_INVALID_PARAM,
337 "Third-party ACDS servers require explicit --discovery-service-key configuration."
338 "Only %s has automatic trust.",
339 ACDS_OFFICIAL_SERVER);
340
341 } else {
342 // Use parse_public_key for all other cases (handles HTTPS, files, github:, gitlab:, etc.)
343 public_key_t key;
344 result = parse_public_key(key_spec, &key);
345 if (result != ASCIICHAT_OK) {
346 return SET_ERRNO(ERROR_CRYPTO_KEY, "Failed to load/download ACDS key from: %s", key_spec);
347 }
348
349 memcpy(new_pubkey, key.key, 32);
350 }
351
352 // ===========================================================================
353 // Step 2: Check cache and handle key changes
354 // ===========================================================================
355
356 uint8_t cached_pubkey[32];
357 result = discovery_keys_load_cached(acds_server, cached_pubkey);
358
359 if (result == ASCIICHAT_OK) {
360 // Cached key exists - compare
361 if (memcmp(cached_pubkey, new_pubkey, 32) != 0) {
362 // Key changed - require user verification
363 result = discovery_keys_verify_change(acds_server, cached_pubkey, new_pubkey);
364 if (result != ASCIICHAT_OK) {
365 return result; // User rejected or error
366 }
367
368 // User accepted - update cache
369 result = discovery_keys_save_cached(acds_server, new_pubkey);
370 if (result != ASCIICHAT_OK) {
371 log_warn("Failed to update cached key, continuing anyway");
372 }
373 } else {
374 log_debug("ACDS key matches cached key for: %s", acds_server);
375 }
376
377 } else {
378 // No cached key - save it for next time
379 log_info("First connection to ACDS server: %s, caching key", acds_server);
380 result = discovery_keys_save_cached(acds_server, new_pubkey);
381 if (result != ASCIICHAT_OK) {
382 log_warn("Failed to cache key, continuing anyway");
383 }
384 }
385
386 // ===========================================================================
387 // Step 3: Return verified key
388 // ===========================================================================
389
390 memcpy(pubkey_out, new_pubkey, 32);
391 log_debug("ACDS key verification successful for: %s", acds_server);
392 return ASCIICHAT_OK;
393}
asciichat_error_t discovery_keys_load_cached(const char *acds_server, uint8_t pubkey_out[32])
asciichat_error_t discovery_keys_verify_change(const char *acds_server, const uint8_t old_pubkey[32], const uint8_t new_pubkey[32])
asciichat_error_t discovery_keys_save_cached(const char *acds_server, const uint8_t pubkey[32])

References discovery_keys_load_cached(), discovery_keys_save_cached(), discovery_keys_verify_change(), and parse_public_key().

Referenced by client_crypto_init().

◆ discovery_keys_verify_change()

asciichat_error_t discovery_keys_verify_change ( const char *  acds_server,
const uint8_t  old_pubkey[32],
const uint8_t  new_pubkey[32] 
)

Definition at line 262 of file discovery_keys.c.

263 {
264 if (!acds_server || !old_pubkey || !new_pubkey) {
265 return SET_ERRNO(ERROR_INVALID_PARAM, "NULL parameter in discovery_keys_verify_change");
266 }
267
268 char old_fingerprint[65], new_fingerprint[65];
269 compute_key_fingerprint(old_pubkey, old_fingerprint);
270 compute_key_fingerprint(new_pubkey, new_fingerprint);
271
272 log_warn("ACDS server key changed for: %s", acds_server);
273
274 log_plain_stderr("\n"
275 "⚠️ WARNING: ACDS SERVER KEY HAS CHANGED\n"
276 "═══════════════════════════════════════════════════════════════\n"
277 "Server: %s\n"
278 "\n"
279 "Old key (SHA256): %s\n"
280 "New key (SHA256): %s\n"
281 "\n"
282 "This could indicate:\n"
283 " 1. The server operator rotated their key\n"
284 " 2. A man-in-the-middle attack is in progress\n"
285 "\n"
286 "Verify the new key fingerprint with the server operator before accepting.\n"
287 "═══════════════════════════════════════════════════════════════\n",
288 acds_server, old_fingerprint, new_fingerprint);
289
290 // Ask user to confirm
291 bool accepted = platform_prompt_yes_no("Accept new ACDS server key", false);
292 if (!accepted) {
293 return SET_ERRNO(ERROR_CRYPTO_VERIFICATION, "User rejected ACDS key change for: %s", acds_server);
294 }
295
296 log_info("User accepted ACDS key change for: %s", acds_server);
297 return ASCIICHAT_OK;
298}
void compute_key_fingerprint(const uint8_t key[ED25519_PUBLIC_KEY_SIZE], char fingerprint[CRYPTO_HEX_KEY_SIZE_NULL])
bool platform_prompt_yes_no(const char *question, bool default_yes)
Definition util.c:81

References compute_key_fingerprint(), and platform_prompt_yes_no().

Referenced by discovery_keys_verify().