nx_room_key.nx source
↩ module page · 87 lines · 3715 B
1// nx_room_key.nx -- ROOM CAPABILITY TOKENS (task #41 ladder: the last table-stakes gap vs the field --
2// LiveKit JWT / Janus+Ant tokens; ours = capability LINKS, sovereign). token = hex16 of sha256(base_room
3// || secret): no server DB, no third party, secret is a FILE (data-driven). Protection is a data-driven
4// LIST (rooms_protected.txt) -- empty list = every room open = today's behavior BY CONSTRUCTION. The lane
5// suffix (#1/#2 shard rooms) is stripped for the check so ONE key covers all lanes of a room. This organ
6// is the SSOT both the relay and the invite tool import -- they cannot drift. license_tier: ORIGINAL
7import "nx_syscalls.nx"
8import "nx_sha256.nx"
9const RK_MAGIC_4096: i64 = 4096
10
11const RK_TOK_LEN: i64 = 16 // hex chars of the first 8 digest bytes
12
13// strip the lane suffix: base length = up to the first '#' (byte 35). "vault#1" -> 5.
14func rk_base_len(room: *u8, n: i64) -> i64 {
15 var i: i64 = 0
16 while i < n { if room[i] == (35 as u8) { return i } i = i + 1 }
17 return n }
18
19// find "?k=" in a path; copy the token (up to cap) into k_out; returns token length or -1.
20func rk_parse_k(path: *u8, n: i64, k_out: *u8, cap: i64) -> i64 {
21 var i: i64 = 0
22 while i + 3 <= n {
23 if path[i] == (63 as u8) { if path[i+1] == (107 as u8) { if path[i+2] == (61 as u8) {
24 var j: i64 = i + 3
25 var w: i64 = 0
26 while j < n {
27 if w >= cap { return 0 - 1 }
28 if path[j] == (38 as u8) { j = n } else { k_out[w] = path[j]; w = w + 1; j = j + 1 }
29 }
30 if w == 0 { return 0 - 1 }
31 return w
32 } } }
33 i = i + 1
34 }
35 return 0 - 1 }
36
37// path length up to (excluding) the query '?' (byte 63) -- the ROOM IDENTITY must never include ?k=...
38func rk_path_no_query(path: *u8, n: i64) -> i64 {
39 var i: i64 = 0
40 while i < n { if path[i] == (63 as u8) { return i } i = i + 1 }
41 return n }
42
43// token = hex16(sha256(base_room || secret)[0..8)). out needs RK_TOK_LEN bytes.
44func rk_token(room: *u8, room_len: i64, secret: *u8, secret_len: i64, out: *u8) -> i64 {
45 let c: *Sha256 = sys_mmap(RK_MAGIC_4096) as *Sha256
46 sha256_init(c)
47 sha256_update(c, room, room_len)
48 sha256_update(c, secret, secret_len)
49 let dg: *u8 = sys_mmap(64)
50 sha256_final(c, dg)
51 let hx: *u8 = "0123456789abcdef" as *u8
52 var i: i64 = 0
53 while i < 8 {
54 out[i*2] = hx[(dg[i] >> 4) & 15]
55 out[i*2+1] = hx[dg[i] & 15]
56 i = i + 1
57 }
58 return RK_TOK_LEN }
59
60// is base room (room[0..blen)) in the protected list (newline-separated)? exact line match.
61func rk_protected(list: *u8, ln: i64, room: *u8, blen: i64) -> i64 {
62 if ln <= 0 { return 0 }
63 if blen <= 0 { return 0 }
64 var i: i64 = 0
65 while i < ln {
66 var e: i64 = i // find line end [i, e)
67 var scanning: i64 = 1
68 while scanning == 1 {
69 if e >= ln { scanning = 0 }
70 else { if list[e] == (10 as u8) { scanning = 0 }
71 else { if list[e] == (13 as u8) { scanning = 0 } else { e = e + 1 } } }
72 }
73 if e - i == blen {
74 var q: i64 = 0
75 var eq: i64 = 1
76 while q < blen { if list[i + q] != room[q] { eq = 0; q = blen } else { q = q + 1 } }
77 if eq == 1 { return 1 }
78 }
79 i = e // skip EOL run to the next line
80 var skipping: i64 = 1
81 while skipping == 1 {
82 if i >= ln { skipping = 0 }
83 else { if list[i] == (10 as u8) { i = i + 1 }
84 else { if list[i] == (13 as u8) { i = i + 1 } else { skipping = 0 } } }
85 }
86 }
87 return 0 }