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>
21static const char base64_table[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
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) {
38 size_t required_size = ((input_len + 2) / 3) * 4 + 1;
39 if (output_size < required_size) {
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];
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];
60 uint32_t triple = (uint32_t)input[i] << 16;
61 if (i + 1 < input_len) {
62 triple |= (uint32_t)input[i + 1] << 8;
65 output[j++] = base64_table[(triple >> 18) & 0x3F];
66 output[j++] = base64_table[(triple >> 12) & 0x3F];
68 if (i + 1 < input_len) {
69 output[j++] = base64_table[(triple >> 6) & 0x3F];
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");
101 uint8_t ipad[SHA1_BLOCK_LENGTH];
102 uint8_t opad[SHA1_BLOCK_LENGTH];
103 uint8_t inner_hash[SHA1_DIGEST_LENGTH];
106 uint8_t key[SHA1_BLOCK_LENGTH];
107 memset(key, 0,
sizeof(key));
109 if (secret_len > SHA1_BLOCK_LENGTH) {
116 memcpy(key, secret, secret_len);
120 for (
int i = 0; i < SHA1_BLOCK_LENGTH; i++) {
121 ipad[i] = key[i] ^ 0x36;
122 opad[i] = key[i] ^ 0x5c;
134 SHA1Update(&ctx, inner_hash, SHA1_DIGEST_LENGTH);
137 *output_len = SHA1_DIGEST_LENGTH;
143 turn_credentials_t *out_credentials) {
144 if (!
session_id || !secret || !out_credentials) {
145 return SET_ERRNO(ERROR_INVALID_PARAM,
"TURN credentials: NULL parameter");
148 if (validity_seconds == 0) {
149 return SET_ERRNO(ERROR_INVALID_PARAM,
"TURN credentials: validity_seconds must be > 0");
153 time_t now = time(NULL);
154 time_t expires_at = now + (time_t)validity_seconds;
157 int username_len =
safe_snprintf(out_credentials->username,
sizeof(out_credentials->username),
"%ld:%s",
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");
164 uint8_t hmac_result[SHA1_DIGEST_LENGTH];
165 unsigned int hmac_len = 0;
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) {
173 if (hmac_len != SHA1_DIGEST_LENGTH) {
174 return SET_ERRNO(ERROR_CRYPTO,
"TURN credentials: unexpected HMAC length %u (expected %u)", hmac_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");
185 out_credentials->expires_at = expires_at;
187 log_debug(
"Generated TURN credentials: username=%s, expires_at=%ld", out_credentials->username, (
long)expires_at);
197 time_t now = time(NULL);
198 return now >= credentials->expires_at;
void SHA1Update(SHA1_CTX *context, const void *dataptr, unsigned int len)
void SHA1Init(SHA1_CTX *context)
void SHA1Final(unsigned char digest[SHA1_DIGEST_LENGTH], SHA1_CTX *context)
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
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)