code wiki / (root) / session_id.nx

session_id.nx source

↩ module page · 105 lines · 2922 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 22import "syscalls.nx" 23import "rand.nx" 24 25const SESSION_ID_LEN: i64 = 32 26 27// Map 6 bits to URL-safe alphabet. 28func si_map(v: i64) -> i64 { 29 if v < 26 { return 0x41 + v } 30 if v < 52 { return 0x61 + (v - 26) } 31 if v < 62 { return 0x30 + (v - 52) } 32 if v == 62 { return 0x2D } // '-' 33 return 0x5F // '_' 34} 35 36// Is byte a valid session-id character? 37func si_is_valid_char(b: i64) -> i64 { 38 if b >= 0x41 { 39 if b <= 0x5A { return 1 } 40 } 41 if b >= 0x61 { 42 if b <= 0x7A { return 1 } 43 } 44 if b >= 0x30 { 45 if b <= 0x39 { return 1 } 46 } 47 if b == 0x2D { return 1 } 48 if b == 0x5F { return 1 } 49 return 0 50} 51 52// Generate an `n`-char session ID. Returns bytes written. 53func session_id_with_len(out: *u8, n: i64) -> i64 { 54 rand_bytes(out, n) 55 var i: i64 = 0 56 while i < n { 57 out[i] = si_map(out[i] & 0x3F) 58 i = i + 1 59 } 60 return n 61} 62 63// Default 32-char session ID. 64func session_id_new(out: *u8) -> i64 { 65 return session_id_with_len(out, SESSION_ID_LEN) 66} 67 68// Format-validate. Returns 1 if id looks well-formed; 0 69// otherwise. Cheap pre-DB-lookup sanity check. 70func session_id_valid(id: *u8, n: i64) -> i64 { 71 if n != SESSION_ID_LEN { return 0 } 72 var i: i64 = 0 73 while i < n { 74 if si_is_valid_char(id[i]) == 0 { return 0 } 75 i = i + 1 76 } 77 return 1 78} 79 80// Compile-only smoke. 81func main() -> i64 { 82 let a: *u8 = sys_mmap(64) 83 let n: i64 = session_id_new(a) 84 if n != 32 { return 1 } 85 if session_id_valid(a, 32) != 1 { return 2 } 86 87 // Two IDs differ. 88 let b: *u8 = sys_mmap(64) 89 session_id_new(b) 90 var diff: i64 = 0 91 var i: i64 = 0 92 while i < 32 { 93 if a[i] != b[i] { diff = 1; break } 94 i = i + 1 95 } 96 if diff != 1 { return 3 } 97 98 // Invalid char rejected. 99 a[0] = 0x21 // '!' 100 if session_id_valid(a, 32) != 0 { return 4 } 101 102 // Wrong length rejected. 103 if session_id_valid(a, 30) != 0 { return 5 } 104 return 0 105}