cwist
Modern C web framework with libttak runtime
Loading...
Searching...
No Matches
ice.c
Go to the documentation of this file.
1
11#include "webrtc_internal.h"
12
13#include <openssl/evp.h>
14#include <openssl/hmac.h>
15#include <openssl/rand.h>
16#include <string.h>
17
20#define STUN_MAGIC 0x2112A442u
21#define STUN_HDR_LEN 20
22#define ATTR_MAPPED_ADDRESS 0x0001
23#define ATTR_USERNAME 0x0006
24#define ATTR_MESSAGE_INTEGRITY 0x0008
25#define ATTR_PRIORITY 0x0024
26#define ATTR_USE_CANDIDATE 0x0025
27#define ATTR_XOR_MAPPED_ADDRESS 0x0020
28#define ATTR_FINGERPRINT 0x8028
29#define ATTR_ICE_CONTROLLED 0x8029
30#define ATTR_ICE_CONTROLLING 0x802A
34static uint32_t crc32_table[256];
36static bool crc32_ready = false;
37
42static void crc32_init(void) {
43 if (crc32_ready)
44 return;
45 for (uint32_t i = 0; i < 256; i++) {
46 uint32_t c = i;
47 for (int k = 0; k < 8; k++)
48 c = (c & 1) ? 0xEDB88320u ^ (c >> 1) : c >> 1;
49 crc32_table[i] = c;
50 }
51 crc32_ready = true;
52}
53
61static uint32_t crc32_update(uint32_t c, const uint8_t *data, size_t len) {
62 crc32_init();
63 for (size_t i = 0; i < len; i++)
64 c = crc32_table[(c ^ data[i]) & 0xFF] ^ (c >> 8);
65 return c;
66}
67
72static uint32_t rd32(const uint8_t *p) {
73 return (uint32_t)p[0] << 24 | (uint32_t)p[1] << 16 | (uint32_t)p[2] << 8 | p[3];
74}
75
79static uint16_t rd16(const uint8_t *p) {
80 return (uint16_t)((uint16_t)p[0] << 8 | p[1]);
81}
82
87static void wr16(uint8_t *p, uint16_t v) {
88 p[0] = (uint8_t)(v >> 8);
89 p[1] = (uint8_t)v;
90}
91
96static void wr32(uint8_t *p, uint32_t v) {
97 p[0] = (uint8_t)(v >> 24);
98 p[1] = (uint8_t)(v >> 16);
99 p[2] = (uint8_t)(v >> 8);
100 p[3] = (uint8_t)v;
101}
102
110int cwist_ice_stun_is_message(const uint8_t *buf, size_t len) {
111 return len >= STUN_HDR_LEN && rd32(buf + 4) == STUN_MAGIC;
112}
113
119bool cwist_ice_stun_is_binding_request(const uint8_t *buf, size_t len) {
120 return cwist_ice_stun_is_message(buf, len) && rd16(buf) == CWIST_STUN_BINDING_REQUEST;
121}
122
131typedef bool (*attr_cb)(uint16_t type, const uint8_t *val, uint16_t val_len, void *arg);
132
141static bool walk_attrs(const uint8_t *msg, size_t len, attr_cb cb, void *arg) {
142 uint16_t msg_len = rd16(msg + 2);
143 if ((size_t)msg_len + STUN_HDR_LEN > len || msg_len % 4 != 0)
144 return false;
145 size_t off = STUN_HDR_LEN;
146 size_t end = STUN_HDR_LEN + msg_len;
147 while (off + 4 <= end) {
148 uint16_t type = rd16(msg + off);
149 uint16_t alen = rd16(msg + off + 2);
150 off += 4;
151 if (off + alen > end)
152 return false;
153 if (!cb(type, msg + off, alen, arg))
154 return false;
155 off += (alen + 3) & ~3u;
156 }
157 return off == end;
158}
159
161typedef struct {
162 bool found;
163 uint16_t val_len;
164 uint16_t type_wanted;
165} attr_find_arg;
166
170static bool attr_find_cb(uint16_t type, const uint8_t *val, uint16_t val_len, void *arg) {
171 (void)val;
172 attr_find_arg *a = arg;
173 if (type == a->type_wanted) {
174 a->found = true;
175 a->val_len = val_len;
176 return false;
177 }
178 return true;
179}
180
187static bool attr_present(const uint8_t *msg, size_t len, uint16_t type) {
188 attr_find_arg a = { .type_wanted = type };
189 walk_attrs(msg, len, attr_find_cb, &a);
190 return a.found;
191}
192
197bool cwist_ice_stun_has_use_candidate(const uint8_t *buf, size_t len) {
198 return attr_present(buf, len, ATTR_USE_CANDIDATE);
199}
200
210static bool find_mi(const uint8_t *msg, size_t len, size_t *mi_off) {
212 return false;
213 uint16_t msg_len = rd16(msg + 2);
214 size_t end = STUN_HDR_LEN + msg_len;
215 size_t off = STUN_HDR_LEN;
216 while (off + 4 <= end) {
217 uint16_t type = rd16(msg + off);
218 uint16_t alen = rd16(msg + off + 2);
219 if (type == ATTR_MESSAGE_INTEGRITY) {
220 *mi_off = off;
221 return alen == 20;
222 }
223 off += 4 + ((alen + 3) & ~3u);
224 }
225 return false;
226}
227
241int cwist_ice_stun_validate_request(const uint8_t *buf, size_t len, const char *pwd) {
242 size_t pwd_len = strlen(pwd);
244 unsigned int hmac_len = 0;
245 size_t mi_off;
246 if (!cwist_ice_stun_is_message(buf, len))
247 return 0;
248 if (!find_mi(buf, len, &mi_off))
249 return 1; /* no MI: accept (RFC 8445 requires it, but stay permissive at MVP) */
250 /* MI covers the message up to and including the MI attribute header, with
251 * the message length field set to end at the MI attribute. */
253 memcpy(hdr, buf, STUN_HDR_LEN);
254 wr16(hdr + 2, (uint16_t)(mi_off - STUN_HDR_LEN + 4 + 20));
255 HMAC_CTX *ctx = HMAC_CTX_new();
256 if (!ctx)
257 return 0;
258 HMAC_Init_ex(ctx, pwd, (int)pwd_len, EVP_sha1(), NULL);
259 HMAC_Update(ctx, hdr, STUN_HDR_LEN);
261 HMAC_Final(ctx, hmac, &hmac_len);
262 HMAC_CTX_free(ctx);
263 return hmac_len == 20 && memcmp(hmac, buf + mi_off + 4, 20) == 0;
264}
265
281int cwist_ice_stun_build_response(uint8_t *out, size_t cap, const uint8_t *req, size_t req_len,
282 const struct sockaddr_in *mapped, const char *pwd) {
283 (void)req_len;
284 uint8_t body[64];
285 size_t blen = 0;
286
287 /* XOR-MAPPED-ADDRESS, IPv4 */
288 wr16(body + blen, ATTR_XOR_MAPPED_ADDRESS);
289 wr16(body + blen + 2, 8);
290 blen += 4;
291 body[blen++] = 0x00;
292 body[blen++] = 0x01; /* family IPv4 */
293 wr16(body + blen, (uint16_t)(ntohs(mapped->sin_port) ^ 0xFFFF));
294 blen += 2;
295 uint32_t addr = ntohl(mapped->sin_addr.s_addr) ^ STUN_MAGIC;
296 wr32(body + blen, addr);
297 blen += 4;
298 while (blen % 4)
299 body[blen++] = 0;
300
301 /* MESSAGE-INTEGRITY over header+body+MI attr header with adjusted length */
302 uint16_t len_for_mi = (uint16_t)(blen + 4 + 20);
304 memset(hdr, 0, STUN_HDR_LEN);
306 wr16(hdr + 2, len_for_mi);
307 wr32(hdr + 4, STUN_MAGIC);
308 memcpy(hdr + 8, req + 8, 12);
309 uint8_t mi[20];
310 unsigned int mi_len = 0;
312 if (!hctx)
313 return -1;
316 HMAC_Update(hctx, body, blen);
319
320 wr16(body + blen, ATTR_MESSAGE_INTEGRITY);
321 wr16(body + blen + 2, 20);
322 blen += 4;
323 memcpy(body + blen, mi, 20);
324 blen += 20;
325
326 /* FINGERPRINT */
329 memcpy(hdr2, hdr, STUN_HDR_LEN);
330 wr16(hdr2 + 2, len_for_fp);
331 uint32_t c = crc32_update(0xFFFFFFFFu, hdr2, STUN_HDR_LEN);
332 c = crc32_update(c, body, blen);
333 c ^= 0xFFFFFFFFu;
334 wr16(body + blen, ATTR_FINGERPRINT);
335 wr16(body + blen + 2, 4);
336 wr32(body + blen + 4, c ^ 0x5354554Eu);
337 blen += 8;
338
339 if (cap < STUN_HDR_LEN + blen)
340 return -1;
341 memcpy(out, hdr2, STUN_HDR_LEN);
342 memcpy(out + STUN_HDR_LEN, body, blen);
343 return (int)(STUN_HDR_LEN + blen);
344}
345
362 const char *username) {
363 size_t ulen = strlen(username);
364 if (cap < STUN_HDR_LEN)
365 return -1;
366 memset(out, 0, STUN_HDR_LEN);
368 wr32(out + 4, STUN_MAGIC);
369 memcpy(out + 8, txid, 12);
370
371 size_t blen = 0;
372 uint8_t body[256];
373 /* SOFTWARE-ish placeholder skipped; USERNAME */
374 if (ulen > 0) {
375 wr16(body + blen, ATTR_USERNAME);
376 wr16(body + blen + 2, (uint16_t)ulen);
377 blen += 4;
378 memcpy(body + blen, username, ulen);
379 blen += ulen;
380 while (blen % 4)
381 body[blen++] = 0;
382 }
383 /* ICE-CONTROLLING (8 bytes) */
384 wr16(body + blen, ATTR_ICE_CONTROLLING);
385 wr16(body + blen + 2, 8);
386 memset(body + blen + 4, 0, 8);
387 blen += 12;
388 /* USE-CANDIDATE */
389 wr16(body + blen, ATTR_USE_CANDIDATE);
390 wr16(body + blen + 2, 0);
391 blen += 4;
392 /* PRIORITY */
393 wr16(body + blen, ATTR_PRIORITY);
394 wr16(body + blen + 2, 4);
395 wr32(body + blen + 4, 0x7EFFFFFFu);
396 blen += 8;
397
398 /* MESSAGE-INTEGRITY + FINGERPRINT added by the caller via _finalize; here we
399 * keep the request unsigned because the MI key (peer pwd) is supplied at
400 * send time. cwist_ice_stun_sign_request() handles it. */
401 wr16(out + 2, (uint16_t)blen);
402 if (cap < STUN_HDR_LEN + blen)
403 return -1;
404 memcpy(out + STUN_HDR_LEN, body, blen);
405 return (int)(STUN_HDR_LEN + blen);
406}
407
423static int stun_sign(uint8_t *msg, size_t cap, size_t msg_len, const char *pwd) {
424 uint8_t body[64];
425 size_t blen = 0;
428 memcpy(hdr, msg, STUN_HDR_LEN);
429 wr16(hdr + 2, len_for_mi);
430 uint8_t mi[20];
431 unsigned int mi_len = 0;
433 if (!hctx)
434 return -1;
440
441 wr16(body + blen, ATTR_MESSAGE_INTEGRITY);
442 wr16(body + blen + 2, 20);
443 blen += 4;
444 memcpy(body + blen, mi, 20);
445 blen += 20;
446
449 memcpy(hdr2, hdr, STUN_HDR_LEN);
450 wr16(hdr2 + 2, len_for_fp);
451 uint32_t c = crc32_update(0xFFFFFFFFu, hdr2, STUN_HDR_LEN);
452 c = crc32_update(c, msg + STUN_HDR_LEN, msg_len - STUN_HDR_LEN);
453 c = crc32_update(c, body, blen);
454 c ^= 0xFFFFFFFFu;
455 wr16(body + blen, ATTR_FINGERPRINT);
456 wr16(body + blen + 2, 4);
457 wr32(body + blen + 4, c ^ 0x5354554Eu);
458 blen += 8;
459
460 if (cap < msg_len + blen)
461 return -1;
462 memcpy(msg + msg_len, body, blen);
464 return (int)(msg_len + blen);
465}
466
474int cwist_ice_stun_sign_request(uint8_t *msg, size_t cap, size_t msg_len, const char *pwd) {
475 return stun_sign(msg, cap, msg_len, pwd);
476}
477
479typedef struct {
480 const uint8_t *mi_val;
481 struct sockaddr_in mapped;
482 bool has_mapped;
483} parse_resp_arg;
484
488static bool parse_resp_cb(uint16_t type, const uint8_t *val, uint16_t val_len, void *arg) {
489 parse_resp_arg *a = arg;
490 if (type == ATTR_MESSAGE_INTEGRITY) {
491 a->mi_val = val;
492 (void)val_len;
493 } else if (type == ATTR_XOR_MAPPED_ADDRESS && val_len >= 8) {
494 a->mapped.sin_family = AF_INET;
495 a->mapped.sin_port = htons((uint16_t)(rd16(val + 2) ^ 0xFFFF));
496 a->mapped.sin_addr.s_addr = htonl(rd32(val + 4) ^ STUN_MAGIC);
497 a->has_mapped = true;
498 }
499 return true;
500}
501
518int cwist_ice_stun_parse_response(const uint8_t *buf, size_t len, const uint8_t txid[12],
519 const char *pwd, struct sockaddr_in *mapped) {
520 if (!cwist_ice_stun_is_message(buf, len) || rd16(buf) != CWIST_STUN_BINDING_RESPONSE)
521 return 0;
522 if (memcmp(buf + 8, txid, 12) != 0)
523 return 0;
524 if (!cwist_ice_stun_validate_request(buf, len, pwd))
525 return 0;
526 parse_resp_arg a = { 0 };
527 if (!walk_attrs(buf, len, parse_resp_cb, &a))
528 return 0;
529 if (!a.mi_val)
530 return 0;
531 if (mapped) {
532 if (!a.has_mapped)
533 return 0;
534 *mapped = a.mapped;
535 }
536 return 1;
537}
538
540static const char b64url_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
541
556int cwist_ice_stun_get_remote_ufrag(const uint8_t *buf, size_t len, char *out, size_t cap) {
557 if (!cwist_ice_stun_is_message(buf, len))
558 return -1;
559 uint16_t msg_len = rd16(buf + 2);
560 size_t end = STUN_HDR_LEN + msg_len;
561 size_t off = STUN_HDR_LEN;
562 while (off + 4 <= end) {
563 uint16_t type = rd16(buf + off);
564 uint16_t alen = rd16(buf + off + 2);
565 if (type == ATTR_USERNAME) {
566 if (off + 4 + alen > end) return -1;
567 const uint8_t *name = buf + off + 4;
568 size_t colon = 0;
569 while (colon < alen && name[colon] != ':') colon++;
570 if (colon == alen) return -1; /* no ':' separator */
571 size_t i = 0;
572 for (size_t j = colon + 1; j < alen && i + 1 < cap; j++) out[i++] = (char)name[j];
573 out[i] = '\0';
574 return 0;
575 }
576 off += 4 + ((alen + 3) & ~3u);
577 }
578 return -1;
579}
580
592void cwist_ice_random_creds(char *ufrag, size_t ufrag_cap, char *pwd, size_t pwd_cap) {
593 uint8_t raw[24];
594 RAND_bytes(raw, sizeof(raw));
595 size_t n = ufrag_cap - 1 < 8 ? ufrag_cap - 1 : 8;
596 for (size_t i = 0; i < n; i++)
597 ufrag[i] = b64url_chars[raw[i] & 63];
598 ufrag[n] = '\0';
599 size_t m = pwd_cap - 1 < 32 ? pwd_cap - 1 : 32;
600 for (size_t i = 0; i < m; i++)
601 pwd[i] = b64url_chars[raw[8 + (i % 16)] & 63];
602 pwd[m] = '\0';
603}
mbedTLS TLS backend: EVP subset (HMAC digest tags, AES-256-CBC).
#define EVP_MAX_MD_SIZE
Definition evp.h:23
mbedTLS TLS backend: HMAC subset (HMAC-SHA256, one-shot + streaming).
#define HMAC_Init_ex
Definition hmac.h:24
struct cwist_mtls_hmac_ctx HMAC_CTX
Definition hmac.h:19
#define HMAC_Update
Definition hmac.h:25
#define HMAC_CTX_new
Definition hmac.h:22
#define HMAC_Final
Definition hmac.h:26
#define HMAC_CTX_free
Definition hmac.h:23
int cwist_ice_stun_parse_response(const uint8_t *buf, size_t len, const uint8_t txid[12], const char *pwd, struct sockaddr_in *mapped)
Validate a received STUN binding response.
Definition ice.c:518
int cwist_ice_stun_is_message(const uint8_t *buf, size_t len)
Check whether a datagram looks like a STUN message.
Definition ice.c:110
#define ATTR_PRIORITY
Definition ice.c:25
bool cwist_ice_stun_has_use_candidate(const uint8_t *buf, size_t len)
Check whether a STUN message carries the USE-CANDIDATE attribute.
Definition ice.c:197
int cwist_ice_stun_sign_request(uint8_t *msg, size_t cap, size_t msg_len, const char *pwd)
Public wrapper around stun_sign() for signing a built binding request.
Definition ice.c:474
int cwist_ice_stun_validate_request(const uint8_t *buf, size_t len, const char *pwd)
Validate the MESSAGE-INTEGRITY of a STUN message against a password.
Definition ice.c:241
#define STUN_HDR_LEN
Definition ice.c:21
int cwist_ice_stun_build_request(uint8_t *out, size_t cap, const uint8_t txid[12], const char *username)
Build an unsigned ICE-controlling STUN binding request.
Definition ice.c:361
#define ATTR_MESSAGE_INTEGRITY
Definition ice.c:24
#define ATTR_USERNAME
Definition ice.c:23
int cwist_ice_stun_get_remote_ufrag(const uint8_t *buf, size_t len, char *out, size_t cap)
Extract the sender's ufrag from the USERNAME attribute of a STUN message.
Definition ice.c:556
bool cwist_ice_stun_is_binding_request(const uint8_t *buf, size_t len)
Check whether a datagram is a STUN binding request.
Definition ice.c:119
void cwist_ice_random_creds(char *ufrag, size_t ufrag_cap, char *pwd, size_t pwd_cap)
Generate random ICE ufrag/pwd credentials.
Definition ice.c:592
#define STUN_MAGIC
Definition ice.c:20
bool(* attr_cb)(uint16_t type, const uint8_t *val, uint16_t val_len, void *arg)
Definition ice.c:131
#define ATTR_FINGERPRINT
Definition ice.c:28
#define ATTR_XOR_MAPPED_ADDRESS
Definition ice.c:27
#define ATTR_ICE_CONTROLLING
Definition ice.c:30
#define ATTR_USE_CANDIDATE
Definition ice.c:26
int cwist_ice_stun_build_response(uint8_t *out, size_t cap, const uint8_t *req, size_t req_len, const struct sockaddr_in *mapped, const char *pwd)
Build a STUN binding response for a received binding request.
Definition ice.c:281
size_t cap
Definition otel.c:56
mbedTLS TLS backend: RAND_bytes over mbedTLS CTR-DRBG.
#define RAND_bytes
Definition rand.h:17
Internal types and cross-file helpers for the WebRTC DataChannel module (ICE-lite + DTLS + SCTP over ...
#define CWIST_STUN_BINDING_RESPONSE
#define CWIST_STUN_BINDING_REQUEST