code wiki / (root) / nx_session_id.nx

nx_session_id.nx source

↩ module page · 111 lines · 3074 B

1// session_id.nx -- cryptographically strong session identifiers. 2// 3// Session IDs are the primary auth artefact after login. They 4// must be: 5// - Unpredictable (>= 128 bits entropy) 6// - URL + cookie-safe characters 7// - Fixed length (makes brute-force scans inherently bounded) 8// - Cheap to validate format before DB lookup 9// 10// This module generates 32-char session IDs = 192 bits entropy 11// over the URL-safe alphabet (A-Z a-z 0-9 - _). Matches the 12// security profile of Django / Rails / Express session IDs. 13// 14// Composes rand.nx. 15// 16// Invariants: 17// SI1 Every char from the 64-char URL-safe alphabet. 18// SI2 Length fixed at 32 (customisable via session_id_with_len). 19// SI3 Format-validation helper so callers can reject malformed 20// IDs before DB lookup. 21 22// nx_safety_envelope: 23// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 24// sil_target: SIL1 25// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 26// verdict: NOT_YET_EVALUATED 27 28import "nx_syscalls.nx" 29import "nx_rand.nx" 30 31const SESSION_ID_LEN: i64 = 32 32 33// Map 6 bits to URL-safe alphabet. 34func si_map(v: i64) -> i64 { 35 if v < 26 { return 0x41 + v } 36 if v < 52 { return 0x61 + (v - 26) } 37 if v < 62 { return 0x30 + (v - 52) } 38 if v == 62 { return 0x2D } // '-' 39 return 0x5F // '_' 40} 41 42// Is byte a valid session-id character? 43func si_is_valid_char(b: i64) -> i64 { 44 if b >= 0x41 { 45 if b <= 0x5A { return 1 } 46 } 47 if b >= 0x61 { 48 if b <= 0x7A { return 1 } 49 } 50 if b >= 0x30 { 51 if b <= 0x39 { return 1 } 52 } 53 if b == 0x2D { return 1 } 54 if b == 0x5F { return 1 } 55 return 0 56} 57 58// Generate an `n`-char session ID. Returns bytes written. 59func session_id_with_len(out: *u8, n: i64) -> i64 { 60 rand_bytes(out, n) 61 var i: i64 = 0 62 while i < n { 63 out[i] = si_map(out[i] & 0x3F) 64 i = i + 1 65 } 66 return n 67} 68 69// Default 32-char session ID. 70func session_id_new(out: *u8) -> i64 { 71 return session_id_with_len(out, SESSION_ID_LEN) 72} 73 74// Format-validate. Returns 1 if id looks well-formed; 0 75// otherwise. Cheap pre-DB-lookup sanity check. 76func session_id_valid(id: *u8, n: i64) -> i64 { 77 if n != SESSION_ID_LEN { return 0 } 78 var i: i64 = 0 79 while i < n { 80 if si_is_valid_char(id[i]) == 0 { return 0 } 81 i = i + 1 82 } 83 return 1 84} 85 86// Compile-only smoke. 87func main() -> i64 { 88 let a: *u8 = sys_mmap(64) 89 let n: i64 = session_id_new(a) 90 if n != 32 { return 1 } 91 if session_id_valid(a, 32) != 1 { return 2 } 92 93 // Two IDs differ. 94 let b: *u8 = sys_mmap(64) 95 session_id_new(b) 96 var diff: i64 = 0 97 var i: i64 = 0 98 while i < 32 { 99 if a[i] != b[i] { diff = 1; break } 100 i = i + 1 101 } 102 if diff != 1 { return 3 } 103 104 // Invalid char rejected. 105 a[0] = 0x21 // '!' 106 if session_id_valid(a, 32) != 0 { return 4 } 107 108 // Wrong length rejected. 109 if session_id_valid(a, 30) != 0 { return 5 } 110 return 0 111}