ascii-chat 0.8.38
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
9#include <ascii-chat/network/webrtc/turn_credentials.h>
10#include <ascii-chat/asciichat_errno.h>
11#include <ascii-chat/common.h>
12#include <ascii-chat/log/logging.h>
13#include <ascii-chat/crypto/sha1.h>
14#include <stdio.h>
15#include <string.h>
16#include <time.h>
17
21static const char base64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
22
32static size_t base64_encode(const uint8_t *input, size_t input_len, char *output, size_t output_size) {
33 if (!input || !output || input_len == 0) {
34 return 0;
35 }
36
37 // Calculate required output size: ((n + 2) / 3) * 4 + 1 for null terminator
38 size_t required_size = ((input_len + 2) / 3) * 4 + 1;
39 if (output_size < required_size) {
40 return 0;
41 }
42
43 size_t i = 0;
44 size_t j = 0;
45
46 // Process input in 3-byte chunks
47 while (i + 2 < input_len) {
48 uint32_t triple = ((uint32_t)input[i] << 16) | ((uint32_t)input[i + 1] << 8) | (uint32_t)input[i + 2];
49
50 output[j++] = base64_table[(triple >> 18) & 0x3F];
51 output[j++] = base64_table[(triple >> 12) & 0x3F];
52 output[j++] = base64_table[(triple >> 6) & 0x3F];
53 output[j++] = base64_table[triple & 0x3F];
54
55 i += 3;
56 }
57
58 // Handle remaining bytes (1 or 2)
59 if (i < input_len) {
60 uint32_t triple = (uint32_t)input[i] << 16;
61 if (i + 1 < input_len) {
62 triple |= (uint32_t)input[i + 1] << 8;
63 }
64
65 output[j++] = base64_table[(triple >> 18) & 0x3F];
66 output[j++] = base64_table[(triple >> 12) & 0x3F];
67
68 if (i + 1 < input_len) {
69 output[j++] = base64_table[(triple >> 6) & 0x3F];
70 } else {
71 output[j++] = '=';
72 }
73 output[j++] = '=';
74 }
75
76 output[j] = '\0';
77 return j;
78}
79
94static asciichat_error_t hmac_sha1(const uint8_t *data, size_t data_len, const uint8_t *secret, size_t secret_len,
95 uint8_t *output, unsigned int *output_len) {
96 if (!data || !secret || !output || !output_len) {
97 return SET_ERRNO(ERROR_INVALID_PARAM, "HMAC-SHA1: NULL parameter");
98 }
99
100 SHA1_CTX ctx;
101 uint8_t ipad[SHA1_BLOCK_LENGTH];
102 uint8_t opad[SHA1_BLOCK_LENGTH];
103 uint8_t inner_hash[SHA1_DIGEST_LENGTH];
104
105 // Prepare key: pad or truncate to block length
106 uint8_t key[SHA1_BLOCK_LENGTH];
107 memset(key, 0, sizeof(key));
108
109 if (secret_len > SHA1_BLOCK_LENGTH) {
110 // If key is longer than block size, hash it first
111 SHA1_CTX key_ctx;
112 SHA1Init(&key_ctx);
113 SHA1Update(&key_ctx, secret, secret_len);
114 SHA1Final(key, &key_ctx);
115 } else {
116 memcpy(key, secret, secret_len);
117 }
118
119 // Prepare ipad and opad (HMAC construction per RFC 2104)
120 for (int i = 0; i < SHA1_BLOCK_LENGTH; i++) {
121 ipad[i] = key[i] ^ 0x36;
122 opad[i] = key[i] ^ 0x5c;
123 }
124
125 // Compute inner hash: H(ipad || message)
126 SHA1Init(&ctx);
127 SHA1Update(&ctx, ipad, SHA1_BLOCK_LENGTH);
128 SHA1Update(&ctx, data, data_len);
129 SHA1Final(inner_hash, &ctx);
130
131 // Compute outer hash: H(opad || inner_hash)
132 SHA1Init(&ctx);
133 SHA1Update(&ctx, opad, SHA1_BLOCK_LENGTH);
134 SHA1Update(&ctx, inner_hash, SHA1_DIGEST_LENGTH);
135 SHA1Final(output, &ctx);
136
137 *output_len = SHA1_DIGEST_LENGTH;
138
139 return ASCIICHAT_OK;
140}
141
142asciichat_error_t turn_generate_credentials(const char *session_id, const char *secret, uint32_t validity_seconds,
143 turn_credentials_t *out_credentials) {
144 if (!session_id || !secret || !out_credentials) {
145 return SET_ERRNO(ERROR_INVALID_PARAM, "TURN credentials: NULL parameter");
146 }
147
148 if (validity_seconds == 0) {
149 return SET_ERRNO(ERROR_INVALID_PARAM, "TURN credentials: validity_seconds must be > 0");
150 }
151
152 // Calculate expiration timestamp
153 time_t now = time(NULL);
154 time_t expires_at = now + (time_t)validity_seconds;
155
156 // Format username: "{timestamp}:{session_id}"
157 int username_len = safe_snprintf(out_credentials->username, sizeof(out_credentials->username), "%ld:%s",
158 (long)expires_at, session_id);
159 if (username_len < 0 || (size_t)username_len >= sizeof(out_credentials->username)) {
160 return SET_ERRNO(ERROR_BUFFER_OVERFLOW, "TURN credentials: username too long");
161 }
162
163 // Compute HMAC-SHA1(secret, username)
164 uint8_t hmac_result[SHA1_DIGEST_LENGTH];
165 unsigned int hmac_len = 0;
166
167 asciichat_error_t result = hmac_sha1((const uint8_t *)out_credentials->username, (size_t)username_len,
168 (const uint8_t *)secret, strlen(secret), hmac_result, &hmac_len);
169 if (result != ASCIICHAT_OK) {
170 return result;
171 }
172
173 if (hmac_len != SHA1_DIGEST_LENGTH) {
174 return SET_ERRNO(ERROR_CRYPTO, "TURN credentials: unexpected HMAC length %u (expected %u)", hmac_len,
175 SHA1_DIGEST_LENGTH);
176 }
177
178 // Base64-encode the HMAC to get the password
179 size_t encoded_len =
180 base64_encode(hmac_result, hmac_len, out_credentials->password, sizeof(out_credentials->password));
181 if (encoded_len == 0) {
182 return SET_ERRNO(ERROR_BUFFER_OVERFLOW, "TURN credentials: password encoding failed");
183 }
184
185 out_credentials->expires_at = expires_at;
186
187 log_debug("Generated TURN credentials: username=%s, expires_at=%ld", out_credentials->username, (long)expires_at);
188
189 return ASCIICHAT_OK;
190}
191
192bool turn_credentials_expired(const turn_credentials_t *credentials) {
193 if (!credentials) {
194 return true;
195 }
196
197 time_t now = time(NULL);
198 return now >= credentials->expires_at;
199}
void SHA1Update(SHA1_CTX *context, const void *dataptr, unsigned int len)
Definition sha1.c:198
void SHA1Init(SHA1_CTX *context)
Definition sha1.c:186
void SHA1Final(unsigned char digest[SHA1_DIGEST_LENGTH], SHA1_CTX *context)
Definition sha1.c:219
uint8_t session_id[16]
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:456
bool turn_credentials_expired(const turn_credentials_t *credentials)
asciichat_error_t turn_generate_credentials(const char *session_id, const char *secret, uint32_t validity_seconds, turn_credentials_t *out_credentials)