ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
turn_credentials.c
Go to the documentation of this file.
1
7#include "asciichat_errno.h"
8#include "common.h"
9#include "log/logging.h"
10
11#include <openssl/evp.h>
12#include <openssl/hmac.h>
13#include <stdio.h>
14#include <string.h>
15#include <time.h>
16
20static const char base64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
21
31static size_t base64_encode(const uint8_t *input, size_t input_len, char *output, size_t output_size) {
32 if (!input || !output || input_len == 0) {
33 return 0;
34 }
35
36 // Calculate required output size: ((n + 2) / 3) * 4 + 1 for null terminator
37 size_t required_size = ((input_len + 2) / 3) * 4 + 1;
38 if (output_size < required_size) {
39 return 0;
40 }
41
42 size_t i = 0;
43 size_t j = 0;
44
45 // Process input in 3-byte chunks
46 while (i + 2 < input_len) {
47 uint32_t triple = ((uint32_t)input[i] << 16) | ((uint32_t)input[i + 1] << 8) | (uint32_t)input[i + 2];
48
49 output[j++] = base64_table[(triple >> 18) & 0x3F];
50 output[j++] = base64_table[(triple >> 12) & 0x3F];
51 output[j++] = base64_table[(triple >> 6) & 0x3F];
52 output[j++] = base64_table[triple & 0x3F];
53
54 i += 3;
55 }
56
57 // Handle remaining bytes (1 or 2)
58 if (i < input_len) {
59 uint32_t triple = (uint32_t)input[i] << 16;
60 if (i + 1 < input_len) {
61 triple |= (uint32_t)input[i + 1] << 8;
62 }
63
64 output[j++] = base64_table[(triple >> 18) & 0x3F];
65 output[j++] = base64_table[(triple >> 12) & 0x3F];
66
67 if (i + 1 < input_len) {
68 output[j++] = base64_table[(triple >> 6) & 0x3F];
69 } else {
70 output[j++] = '=';
71 }
72 output[j++] = '=';
73 }
74
75 output[j] = '\0';
76 return j;
77}
78
90static asciichat_error_t hmac_sha1(const uint8_t *data, size_t data_len, const uint8_t *secret, size_t secret_len,
91 uint8_t *output, unsigned int *output_len) {
92 if (!data || !secret || !output || !output_len) {
93 return SET_ERRNO(ERROR_INVALID_PARAM, "HMAC-SHA1: NULL parameter");
94 }
95
96 // OpenSSL 3.0+ uses EVP interface, older versions use HMAC_* functions
97#if OPENSSL_VERSION_NUMBER >= 0x30000000L
98 EVP_MAC *mac = EVP_MAC_fetch(NULL, "HMAC", NULL);
99 if (!mac) {
100 return SET_ERRNO(ERROR_CRYPTO, "HMAC-SHA1: Failed to fetch HMAC algorithm");
101 }
102
103 EVP_MAC_CTX *ctx = EVP_MAC_CTX_new(mac);
104 if (!ctx) {
105 EVP_MAC_free(mac);
106 return SET_ERRNO(ERROR_CRYPTO, "HMAC-SHA1: Failed to create context");
107 }
108
109 // Set digest algorithm to SHA1
110 OSSL_PARAM params[] = {OSSL_PARAM_construct_utf8_string("digest", "SHA1", 0), OSSL_PARAM_construct_end()};
111
112 if (EVP_MAC_init(ctx, secret, secret_len, params) != 1) {
113 EVP_MAC_CTX_free(ctx);
114 EVP_MAC_free(mac);
115 return SET_ERRNO(ERROR_CRYPTO, "HMAC-SHA1: Failed to initialize");
116 }
117
118 if (EVP_MAC_update(ctx, data, data_len) != 1) {
119 EVP_MAC_CTX_free(ctx);
120 EVP_MAC_free(mac);
121 return SET_ERRNO(ERROR_CRYPTO, "HMAC-SHA1: Failed to update");
122 }
123
124 size_t out_len = 0;
125 if (EVP_MAC_final(ctx, output, &out_len, 20) != 1) {
126 EVP_MAC_CTX_free(ctx);
127 EVP_MAC_free(mac);
128 return SET_ERRNO(ERROR_CRYPTO, "HMAC-SHA1: Failed to finalize");
129 }
130
131 *output_len = (unsigned int)out_len;
132
133 EVP_MAC_CTX_free(ctx);
134 EVP_MAC_free(mac);
135#else
136 // OpenSSL 1.x compatibility
137 unsigned char *result = HMAC(EVP_sha1(), secret, (int)secret_len, data, data_len, output, output_len);
138 if (!result) {
139 return SET_ERRNO(ERROR_CRYPTO, "HMAC-SHA1: Failed to compute HMAC");
140 }
141#endif
142
143 return ASCIICHAT_OK;
144}
145
146asciichat_error_t turn_generate_credentials(const char *session_id, const char *secret, uint32_t validity_seconds,
147 turn_credentials_t *out_credentials) {
148 if (!session_id || !secret || !out_credentials) {
149 return SET_ERRNO(ERROR_INVALID_PARAM, "TURN credentials: NULL parameter");
150 }
151
152 if (validity_seconds == 0) {
153 return SET_ERRNO(ERROR_INVALID_PARAM, "TURN credentials: validity_seconds must be > 0");
154 }
155
156 // Calculate expiration timestamp
157 time_t now = time(NULL);
158 time_t expires_at = now + (time_t)validity_seconds;
159
160 // Format username: "{timestamp}:{session_id}"
161 int username_len =
162 snprintf(out_credentials->username, sizeof(out_credentials->username), "%ld:%s", (long)expires_at, session_id);
163 if (username_len < 0 || (size_t)username_len >= sizeof(out_credentials->username)) {
164 return SET_ERRNO(ERROR_BUFFER_OVERFLOW, "TURN credentials: username too long");
165 }
166
167 // Compute HMAC-SHA1(secret, username)
168 uint8_t hmac_result[20]; // SHA1 produces 20 bytes
169 unsigned int hmac_len = 0;
170
171 asciichat_error_t result = hmac_sha1((const uint8_t *)out_credentials->username, (size_t)username_len,
172 (const uint8_t *)secret, strlen(secret), hmac_result, &hmac_len);
173 if (result != ASCIICHAT_OK) {
174 return result;
175 }
176
177 if (hmac_len != 20) {
178 return SET_ERRNO(ERROR_CRYPTO, "TURN credentials: unexpected HMAC length %u (expected 20)", hmac_len);
179 }
180
181 // Base64-encode the HMAC to get the password
182 size_t encoded_len =
183 base64_encode(hmac_result, hmac_len, out_credentials->password, sizeof(out_credentials->password));
184 if (encoded_len == 0) {
185 return SET_ERRNO(ERROR_BUFFER_OVERFLOW, "TURN credentials: password encoding failed");
186 }
187
188 out_credentials->expires_at = expires_at;
189
190 log_debug("Generated TURN credentials: username=%s, expires_at=%ld", out_credentials->username, (long)expires_at);
191
192 return ASCIICHAT_OK;
193}
194
196 if (!credentials) {
197 return true;
198 }
199
200 time_t now = time(NULL);
201 return now >= credentials->expires_at;
202}
⚠️‼️ Error and/or exit() when things go bad.
unsigned int uint32_t
Definition common.h:58
unsigned char uint8_t
Definition common.h:56
#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_CRYPTO
Definition error_codes.h:88
@ ERROR_INVALID_PARAM
@ ERROR_BUFFER_OVERFLOW
Definition error_codes.h:98
#define log_debug(...)
Log a DEBUG message.
📝 Logging API with multiple log levels and terminal output control
uint8_t session_id[16]
TURN server credentials (username + password)
⏱️ High-precision timing utilities using sokol_time.h and uthash
bool turn_credentials_expired(const turn_credentials_t *credentials)
Check if TURN credentials have expired.
asciichat_error_t turn_generate_credentials(const char *session_id, const char *secret, uint32_t validity_seconds, turn_credentials_t *out_credentials)
Generate time-limited TURN credentials.
TURN server credential generation for WebRTC.