websocket_handshake.nx source
↩ module page · 80 lines · 2626 B
1// websocket_handshake.nx -- compute Sec-WebSocket-Accept.
2//
3// RFC 6455 §4.2.2: the server's response to a WebSocket upgrade
4// request includes a Sec-WebSocket-Accept header whose value is
5//
6// base64( sha1( client_key || GUID ) )
7//
8// where GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" is a
9// literal defined in the RFC (solely to make accidental
10// handshake-value forgery harder -- it doesn't add security).
11// client_key is the value of the Sec-WebSocket-Key request
12// header (a 16-byte random value, base64-encoded by the client,
13// so 24 ASCII chars including "=" padding).
14//
15// Composes sha1.nx + base64.nx. The concatenation buffer is at
16// most 24 + 36 = 60 bytes.
17//
18// Invariants:
19// WH1 Caller passes the raw client key bytes (typically 24
20// ASCII characters); we don't decode them -- the spec
21// says concatenate the client-sent base64 string AS IS
22// with the GUID string and hash that.
23// WH2 Output is 28 ASCII characters (base64 of 20 bytes).
24
25import "syscalls.nx"
26import "nx_sha1.nx"
27import "base64.nx"
28
29// RFC 6455 GUID -- 36 bytes.
30func ws_guid() -> *u8 {
31 return "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
32}
33
34const WS_GUID_LEN: i64 = 36
35const WS_SHA1_BYTES: i64 = 20
36const WS_ACCEPT_LEN: i64 = 28
37
38// Compute the Sec-WebSocket-Accept value. `key` is the value of
39// the client's Sec-WebSocket-Key header (raw ASCII bytes). Out
40// must have room for 28 base64 chars.
41func ws_accept(key: *u8, key_len: i64, out: *u8) -> i64 {
42 // Build key || GUID in a scratch buffer.
43 let scratch: *u8 = sys_mmap(128)
44 var i: i64 = 0
45 while i < key_len {
46 scratch[i] = key[i]
47 i = i + 1
48 }
49 let guid: *u8 = ws_guid()
50 var j: i64 = 0
51 while j < WS_GUID_LEN {
52 scratch[key_len + j] = guid[j]
53 j = j + 1
54 }
55
56 // SHA-1 of the concatenation -> 20 bytes.
57 let hash: *u8 = sys_mmap(32)
58 sha1(scratch, key_len + WS_GUID_LEN, hash)
59
60 // Base64-encode the hash -> 28 chars.
61 b64_encode(hash, WS_SHA1_BYTES, out)
62 return WS_ACCEPT_LEN
63}
64
65// Compile-only smoke. RFC 6455 §1.3 worked example:
66// client key: "dGhlIHNhbXBsZSBub25jZQ=="
67// accept: "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="
68func main() -> i64 {
69 let out: *u8 = sys_mmap(64)
70 let n: i64 = ws_accept("dGhlIHNhbXBsZSBub25jZQ==", 24, out)
71 if n != WS_ACCEPT_LEN { return 1 }
72
73 let expected: *u8 = "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="
74 var i: i64 = 0
75 while i < WS_ACCEPT_LEN {
76 if out[i] != expected[i] { return 10 + i }
77 i = i + 1
78 }
79 return 0
80}