ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
identity.c
Go to the documentation of this file.
1
6#include "acds/identity.h"
7#include "crypto/crypto.h"
8#include "log/logging.h"
10#include "platform/fs.h"
11#include <sodium.h>
12#include <stdio.h>
13#include <string.h>
14#include <errno.h>
15#include <sys/stat.h>
16
17#ifdef _WIN32
18#include <direct.h> // For _mkdir
19#endif
20
22 if (!public_key || !secret_key) {
23 return SET_ERRNO(ERROR_INVALID_PARAM, "public_key and secret_key cannot be NULL");
24 }
25
26 // Generate Ed25519 keypair using libsodium
27 if (crypto_sign_keypair(public_key, secret_key) != 0) {
28 return SET_ERRNO(ERROR_CRYPTO, "Failed to generate Ed25519 keypair");
29 }
30
31 log_debug("Generated new Ed25519 identity keypair");
32 return ASCIICHAT_OK;
33}
34
35asciichat_error_t acds_identity_load(const char *path, uint8_t public_key[32], uint8_t secret_key[64]) {
36 if (!path || !public_key || !secret_key) {
37 return SET_ERRNO(ERROR_INVALID_PARAM, "path, public_key, and secret_key cannot be NULL");
38 }
39
40 // Open file for reading
41 FILE *fp = fopen(path, "rb");
42 if (!fp) {
43 if (errno == ENOENT) {
44 return SET_ERRNO(ERROR_CONFIG, "Identity file does not exist: %s", path);
45 }
46 return SET_ERRNO_SYS(ERROR_CONFIG, "Failed to open identity file: %s", path);
47 }
48
49 // Read secret key (64 bytes)
50 size_t read = fread(secret_key, 1, 64, fp);
51 if (read != 64) {
52 fclose(fp);
53 return SET_ERRNO(ERROR_CONFIG, "Identity file corrupted (expected 64 bytes, got %zu): %s", read, path);
54 }
55
56 // Extract public key from secret key (last 32 bytes of Ed25519 secret key)
57 memcpy(public_key, secret_key + 32, 32);
58
59 fclose(fp);
60 log_info("Loaded identity from %s", path);
61 return ASCIICHAT_OK;
62}
63
72static asciichat_error_t ensure_directory_exists(const char *path) {
73 if (!path || path[0] == '\0') {
74 return ASCIICHAT_OK;
75 }
76
77 char tmp[512];
78 SAFE_STRNCPY(tmp, path, sizeof(tmp));
79
80 // Determine directory separator based on platform
81 const char sep =
82#ifdef _WIN32
83 '\\'
84#else
85 '/'
86#endif
87 ;
88
89 // Create each directory in the path
90 for (char *p = tmp + 1; *p; p++) {
91 if (*p == sep || *p == '/' || *p == '\\') {
92 char orig = *p;
93 *p = '\0';
94
95 // Skip empty components and root
96 if (tmp[0] != '\0' && strcmp(tmp, ".") != 0) {
97#ifdef _WIN32
98 // Windows: CreateDirectory or _mkdir
99 if (_mkdir(tmp) != 0 && errno != EEXIST) {
100 // Ignore EEXIST (directory already exists)
101 if (errno != ENOENT) { // But propagate other errors
102 log_debug("Failed to create directory: %s (errno=%d)", tmp, errno);
103 }
104 }
105#else
106 // Unix: mkdir with 0700 permissions
107 if (mkdir(tmp, 0700) != 0 && errno != EEXIST) {
108 // Ignore EEXIST (directory already exists)
109 if (errno != ENOENT) { // But propagate other errors
110 log_debug("Failed to create directory: %s (errno=%d)", tmp, errno);
111 }
112 }
113#endif
114 }
115
116 *p = orig;
117 }
118 }
119
120 // Create the final directory
121#ifdef _WIN32
122 if (_mkdir(tmp) != 0 && errno != EEXIST) {
123 if (errno != ENOENT) {
124 return SET_ERRNO_SYS(ERROR_CONFIG, "Failed to create directory: %s", tmp);
125 }
126 }
127#else
128 if (mkdir(tmp, 0700) != 0 && errno != EEXIST) {
129 if (errno != ENOENT) {
130 return SET_ERRNO_SYS(ERROR_CONFIG, "Failed to create directory: %s", tmp);
131 }
132 }
133#endif
134
135 return ASCIICHAT_OK;
136}
137
138asciichat_error_t acds_identity_save(const char *path, const uint8_t public_key[32], const uint8_t secret_key[64]) {
139 if (!path || !public_key || !secret_key) {
140 return SET_ERRNO(ERROR_INVALID_PARAM, "path, public_key, and secret_key cannot be NULL");
141 }
142
143 // Extract directory path and create all parent directories
144 char dir_path[512];
145 SAFE_STRNCPY(dir_path, path, sizeof(dir_path));
146
147 // Find last directory separator
148 char *last_sep = strrchr(dir_path, '/');
149 if (!last_sep) {
150 last_sep = strrchr(dir_path, '\\');
151 }
152
153 if (last_sep) {
154 *last_sep = '\0';
155
156 // Create directory recursively (mkdir -p equivalent)
157 asciichat_error_t result = ensure_directory_exists(dir_path);
158 if (result != ASCIICHAT_OK) {
159 return result;
160 }
161 }
162
163 // Open file for writing (mode 0600 = owner read/write only)
164 int fd = platform_open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
165 if (fd < 0) {
166 return SET_ERRNO_SYS(ERROR_CONFIG, "Failed to create identity file: %s", path);
167 }
168
169 // Write secret key (64 bytes)
170 ssize_t written = write(fd, secret_key, 64);
171 close(fd);
172
173 if (written != 64) {
174 return SET_ERRNO(ERROR_CONFIG, "Failed to write identity file (wrote %zd/64 bytes): %s", written, path);
175 }
176
177 log_info("Saved identity to %s", path);
178 return ASCIICHAT_OK;
179}
180
181void acds_identity_fingerprint(const uint8_t public_key[32], char fingerprint[65]) {
182 if (!public_key || !fingerprint) {
183 log_error("acds_identity_fingerprint: NULL parameters");
184 return;
185 }
186
187 // Compute SHA256 hash of public key
188 uint8_t hash[32];
189 crypto_hash_sha256(hash, public_key, 32);
190
191 // Convert to hex string
192 for (int i = 0; i < 32; i++) {
193 sprintf(&fingerprint[i * 2], "%02x", hash[i]);
194 }
195 fingerprint[64] = '\0';
196}
197
198asciichat_error_t acds_identity_default_path(char *path_out, size_t path_size) {
199 if (!path_out || path_size == 0) {
200 return SET_ERRNO(ERROR_INVALID_PARAM, "path_out cannot be NULL and path_size must be > 0");
201 }
202
203#ifdef _WIN32
204 // Windows: %APPDATA%\ascii-chat\acds_identity
205 const char *appdata = SAFE_GETENV("APPDATA");
206 if (!appdata) {
207 return SET_ERRNO(ERROR_CONFIG, "APPDATA environment variable not set");
208 }
209
210 int written = snprintf(path_out, path_size, "%s\\ascii-chat\\acds_identity", appdata);
211 if (written < 0 || (size_t)written >= path_size) {
212 return SET_ERRNO(ERROR_CONFIG, "Path buffer too small");
213 }
214#else
215 // Unix: ~/.config/ascii-chat/acds_identity
216 const char *home = SAFE_GETENV("HOME");
217 if (!home) {
218 return SET_ERRNO(ERROR_CONFIG, "HOME environment variable not set");
219 }
220
221 // Check for XDG_CONFIG_HOME
222 const char *xdg_config = SAFE_GETENV("XDG_CONFIG_HOME");
223 if (xdg_config && xdg_config[0] != '\0') {
224 int written = snprintf(path_out, path_size, "%s/ascii-chat/acds_identity", xdg_config);
225 if (written < 0 || (size_t)written >= path_size) {
226 return SET_ERRNO(ERROR_CONFIG, "Path buffer too small");
227 }
228 } else {
229 int written = snprintf(path_out, path_size, "%s/.config/ascii-chat/acds_identity", home);
230 if (written < 0 || (size_t)written >= path_size) {
231 return SET_ERRNO(ERROR_CONFIG, "Path buffer too small");
232 }
233 }
234#endif
235
236 return ASCIICHAT_OK;
237}
🔌 Cross-platform abstraction layer umbrella header for ascii-chat
Cross-platform file system operations.
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:358
#define SAFE_GETENV(name)
Definition common.h:378
unsigned char uint8_t
Definition common.h:56
#define SET_ERRNO_SYS(code, context_msg,...)
Set error code with custom message and system error context.
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:46
@ ASCIICHAT_OK
Definition error_codes.h:48
@ ERROR_CONFIG
Definition error_codes.h:54
@ ERROR_CRYPTO
Definition error_codes.h:88
@ ERROR_INVALID_PARAM
#define log_error(...)
Log an ERROR message.
#define log_info(...)
Log an INFO message.
#define log_debug(...)
Log a DEBUG message.
int platform_open(const char *pathname, int flags,...)
Safe file open (open replacement)
int errno
asciichat_error_t acds_identity_load(const char *path, uint8_t public_key[32], uint8_t secret_key[64])
Load identity from file.
Definition identity.c:35
asciichat_error_t acds_identity_default_path(char *path_out, size_t path_size)
Get default identity file path for current platform.
Definition identity.c:198
asciichat_error_t acds_identity_save(const char *path, const uint8_t public_key[32], const uint8_t secret_key[64])
Save identity to file.
Definition identity.c:138
void acds_identity_fingerprint(const uint8_t public_key[32], char fingerprint[65])
Compute SHA256 fingerprint of public key.
Definition identity.c:181
asciichat_error_t acds_identity_generate(uint8_t public_key[32], uint8_t secret_key[64])
Generate new Ed25519 keypair.
Definition identity.c:21
Identity key management for discovery server.
📝 Logging API with multiple log levels and terminal output control