Skip to content

Commit d465167

Browse files
refactor: use openssl for seed
1 parent 61fb3e9 commit d465167

File tree

9 files changed

+72
-58
lines changed

9 files changed

+72
-58
lines changed

examples/websocket_client/main.mbt

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,7 @@ pub async fn connect_to_echo_server() -> Unit {
4242
// Receive echo response
4343
let response = client.receive()
4444
match response {
45-
@websocket.Text(text) => {
46-
println("Received: \{text}")
47-
}
45+
@websocket.Text(text) => println("Received: \{text}")
4846
@websocket.Binary(data) =>
4947
println("Received binary data (\{data.length()} bytes)")
5048
}
@@ -60,19 +58,11 @@ pub async fn connect_to_echo_server() -> Unit {
6058
client.send_binary(binary_data)
6159
let binary_response = client.receive()
6260
match binary_response {
63-
@websocket.Text(text) => {
64-
println("Received text response: \{text}")
65-
}
61+
@websocket.Text(text) => println("Received text response: \{text}")
6662
@websocket.Binary(data) =>
67-
println(
68-
"Received binary response (\{data.length()} bytes)",
69-
)
63+
println("Received binary response (\{data.length()} bytes)")
7064
}
7165

72-
// Test ping
73-
println("Sending ping...")
74-
client.ping()
75-
7666
// Close the connection
7767
println("Closing connection...")
7868
client.close()

src/tls/ffi.mbt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,9 @@ fn err_get_error() -> String {
143143
let len = err_get_error_ffi(buf)
144144
@bytes_util.ascii_to_string(buf[:len])
145145
}
146+
147+
///|
148+
/// Generate cryptographically secure random bytes using OpenSSL's RAND_bytes
149+
/// Returns 1 on success, 0 on failure
150+
#borrow(buf)
151+
pub extern "C" fn rand_bytes(buf : FixedArray[Byte], num : Int) -> Int = "moonbitlang_async_tls_rand_bytes"

src/tls/pkg.generated.mbti

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import(
66
)
77

88
// Values
9+
fn rand_bytes(FixedArray[Byte], Int) -> Int
910

1011
// Errors
1112
pub suberror ConnectionClosed

src/tls/stub.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ typedef struct SSL_METHOD SSL_METHOD;
6969
IMPORT_FUNC(int, SSL_CTX_set_default_verify_paths, (SSL_CTX *ctx))\
7070
IMPORT_FUNC(unsigned long, ERR_get_error, (void))\
7171
IMPORT_FUNC(char *, ERR_error_string, (unsigned long e, char *buf))\
72+
IMPORT_FUNC(int, RAND_bytes, (unsigned char *buf, int num))\
7273

7374
#define IMPORT_FUNC(ret, name, params) static ret (*name) params;
7475
IMPORTED_OPEN_SSL_FUNCTIONS
@@ -250,3 +251,7 @@ int moonbitlang_async_tls_get_error(void *buf) {
250251
ERR_error_string(code, buf);
251252
return strlen(buf);
252253
}
254+
255+
int moonbitlang_async_tls_rand_bytes(unsigned char *buf, int num) {
256+
return RAND_bytes(buf, num);
257+
}

src/websocket/client.mbt

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
/// WebSocket client connection
1717
struct Client {
1818
conn : @socket.Tcp
19+
rand : @random.Rand
1920
mut closed : CloseCode?
2021
}
2122

@@ -37,12 +38,22 @@ pub async fn Client::connect(
3738
port? : Int = 80,
3839
headers? : Map[String, String] = {},
3940
) -> Client {
41+
let seed = FixedArray::make(32, b'\x00')
42+
if @tls.rand_bytes(seed, 32) != 1 {
43+
fail("Failed to get random bytes for WebSocket client")
44+
}
45+
let rand = @random.Rand::chacha8(seed=seed.unsafe_reinterpret_as_bytes())
4046
// Connect TCP socket
4147
let addr = @socket.Addr::parse("\{host}:\{port}")
4248
let conn = @socket.Tcp::connect(addr)
4349
4450
// Send WebSocket handshake request
45-
let key = "dGhlIHNhbXBsZSBub25jZQ==" // In production, generate random key
51+
let nonce = FixedArray::make(16, b'\x00')
52+
nonce.unsafe_write_uint32_le(0, rand.uint())
53+
nonce.unsafe_write_uint32_le(4, rand.uint())
54+
nonce.unsafe_write_uint32_le(8, rand.uint())
55+
nonce.unsafe_write_uint32_le(12, rand.uint())
56+
let key = base64_encode(nonce.unsafe_reinterpret_as_bytes())
4657
let request = "GET \{path} HTTP/1.1\r\n"
4758
conn.write(request)
4859
conn.write("Host: \{host}\r\n")
@@ -79,7 +90,7 @@ pub async fn Client::connect(
7990
"Server response does not contain websocket upgrade confirmation",
8091
)
8192
}
82-
{ conn, closed: None }
93+
{ conn, closed: None, rand }
8394
}
8495
8596
///|
@@ -98,7 +109,13 @@ pub async fn Client::send_text(self : Client, text : String) -> Unit {
98109
raise ConnectionClosed(code)
99110
}
100111
let payload = @encoding/utf8.encode(text)
101-
write_frame(self.conn, true, OpCode::Text, payload, true)
112+
write_frame(
113+
self.conn,
114+
true,
115+
OpCode::Text,
116+
payload,
117+
self.rand.int().to_le_bytes(),
118+
)
102119
}
103120
104121
///|
@@ -107,7 +124,13 @@ pub async fn Client::send_binary(self : Client, data : Bytes) -> Unit {
107124
if self.closed is Some(code) {
108125
raise ConnectionClosed(code)
109126
}
110-
write_frame(self.conn, true, OpCode::Binary, data, true)
127+
write_frame(
128+
self.conn,
129+
true,
130+
OpCode::Binary,
131+
data,
132+
self.rand.int().to_le_bytes(),
133+
)
111134
}
112135
113136
///|
@@ -119,7 +142,13 @@ async fn Client::_ping(self : Client, data? : Bytes = Bytes::new(0)) -> Unit {
119142
if self.closed is Some(code) {
120143
raise ConnectionClosed(code)
121144
}
122-
write_frame(self.conn, true, OpCode::Ping, data, true)
145+
write_frame(
146+
self.conn,
147+
true,
148+
OpCode::Ping,
149+
data,
150+
self.rand.int().to_le_bytes(),
151+
)
123152
}
124153
125154
///|
@@ -130,7 +159,13 @@ async fn Client::pong(self : Client, data? : Bytes = Bytes::new(0)) -> Unit {
130159
if self.closed is Some(code) {
131160
raise ConnectionClosed(code)
132161
}
133-
write_frame(self.conn, true, OpCode::Pong, data, true)
162+
write_frame(
163+
self.conn,
164+
true,
165+
OpCode::Pong,
166+
data,
167+
self.rand.int().to_be_bytes(),
168+
)
134169
}
135170
136171
///|

src/websocket/frame.mbt

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ async fn[R : @io.Reader] read_frame(reader : R) -> Frame {
6767
// Unmask payload if needed
6868
if mask is Some(mask_bytes) {
6969
let payload_arr = payload_bytes.to_fixedarray()
70-
mask_payload(payload_arr, mask_bytes.to_fixedarray())
70+
mask_payload(payload_arr, mask_bytes)
7171
{ fin, opcode, payload: payload_arr.unsafe_reinterpret_as_bytes() }
7272
} else {
7373
{ fin, opcode, payload: payload_bytes }
@@ -81,7 +81,7 @@ async fn[W : @io.Writer] write_frame(
8181
fin : Bool,
8282
opcode : OpCode,
8383
payload : BytesView,
84-
masked : Bool,
84+
mask : Bytes,
8585
) -> Unit {
8686
let payload_len = payload.length().to_int64()
8787
let mut header_len = 2
@@ -94,11 +94,7 @@ async fn[W : @io.Writer] write_frame(
9494
}
9595

9696
// Build header
97-
let header = if masked {
98-
FixedArray::make(header_len + 4, b'\x00')
99-
} else {
100-
FixedArray::make(header_len, b'\x00')
101-
}
97+
let header = FixedArray::make(header_len + mask.length(), b'\x00')
10298

10399
// First byte: FIN + opcode
104100
header[0] = if fin {
@@ -108,7 +104,7 @@ async fn[W : @io.Writer] write_frame(
108104
}
109105

110106
// Second byte: MASK + payload length
111-
let mask_bit = if masked { 0x80 } else { 0 }
107+
let mask_bit = if mask.length() > 0 { 0x80 } else { 0 }
112108
if payload_len < 126L {
113109
header[1] = (mask_bit | payload_len.to_int()).to_byte()
114110
} else if payload_len <= 65535L {
@@ -120,8 +116,7 @@ async fn[W : @io.Writer] write_frame(
120116
}
121117

122118
// Add masking key and mask payload if needed
123-
let final_payload = if masked {
124-
let mask = generate_mask()
119+
let final_payload = if mask.length() > 0 {
125120
for i = 0; i < 4; i = i + 1 {
126121
header[header_len + i] = mask[i]
127122
}

src/websocket/moon.pkg.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"import": [
33
"moonbitlang/async/io",
44
"moonbitlang/async/socket",
5-
"moonbitlang/async/internal/time",
5+
"moonbitlang/async/tls",
66
"moonbitlang/x/crypto",
77
"moonbitlang/async",
88
"moonbitlang/async/aqueue",

src/websocket/server.mbt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ pub async fn ServerConnection::send_text(
289289
self.semaphore.acquire()
290290
defer self.semaphore.release()
291291
let payload = @encoding/utf8.encode(text)
292-
write_frame(self.conn, true, OpCode::Text, payload, false)
292+
write_frame(self.conn, true, OpCode::Text, payload, [])
293293
}
294294

295295
///|
@@ -303,7 +303,7 @@ pub async fn ServerConnection::send_binary(
303303
}
304304
self.semaphore.acquire()
305305
defer self.semaphore.release()
306-
write_frame(self.conn, true, OpCode::Binary, data, false)
306+
write_frame(self.conn, true, OpCode::Binary, data, [])
307307
}
308308

309309
///|
@@ -320,7 +320,7 @@ async fn ServerConnection::_ping(
320320
}
321321
self.semaphore.acquire()
322322
defer self.semaphore.release()
323-
write_frame(self.conn, true, OpCode::Ping, data, false)
323+
write_frame(self.conn, true, OpCode::Ping, data, [])
324324
}
325325

326326
///|
@@ -336,7 +336,7 @@ async fn ServerConnection::pong(
336336
}
337337
self.semaphore.acquire()
338338
defer self.semaphore.release()
339-
write_frame(self.conn, true, OpCode::Pong, data, false)
339+
write_frame(self.conn, true, OpCode::Pong, data, [])
340340
}
341341

342342
///|
@@ -368,7 +368,7 @@ pub async fn ServerConnection::send_close(
368368
true,
369369
OpCode::Close,
370370
payload.unsafe_reinterpret_as_bytes(),
371-
false,
371+
[],
372372
)
373373
self.closed = Some(code)
374374
}

src/websocket/utils.mbt

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,32 +14,12 @@
1414

1515
///|
1616
/// Apply XOR mask to payload data
17-
fn mask_payload(data : FixedArray[Byte], mask : FixedArray[Byte]) -> Unit {
17+
fn mask_payload(data : FixedArray[Byte], mask : Bytes) -> Unit {
1818
for i = 0; i < data.length(); i = i + 1 {
1919
data[i] = data[i] ^ mask[i % 4]
2020
}
2121
}
2222

23-
///|
24-
/// Generate a random 4-byte masking key
25-
fn generate_mask() -> FixedArray[Byte] {
26-
let mask = FixedArray::make(4, b'\x00')
27-
// Use current time as seed for simple randomness
28-
// In production, consider using a cryptographically secure random source
29-
let t = @time.ms_since_epoch()
30-
31-
// Create more entropy by combining time with simple operations
32-
let seed1 = t
33-
let seed2 = t ^ (t >> 13)
34-
let seed3 = seed2 ^ (seed2 << 7)
35-
let seed4 = seed3 ^ (seed3 >> 17)
36-
mask[0] = (seed1 & 0xFF).to_byte()
37-
mask[1] = (seed2 & 0xFF).to_byte()
38-
mask[2] = (seed3 & 0xFF).to_byte()
39-
mask[3] = (seed4 & 0xFF).to_byte()
40-
mask
41-
}
42-
4323
///|
4424
/// Base64 encoding
4525
fn base64_encode(data : Bytes) -> String {
@@ -75,12 +55,14 @@ fn base64_encode(data : Bytes) -> String {
7555
result
7656
}
7757

58+
///|
59+
const MAGIC = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
60+
7861
///|
7962
/// Generate WebSocket accept key from client key using SHA-1 and base64
8063
fn generate_accept_key(client_key : String) -> String {
8164
// WebSocket magic string as defined in RFC 6455
82-
let magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
83-
let combined = client_key + magic
65+
let combined = client_key + MAGIC
8466
let combined_bytes = @encoding/utf8.encode(combined)
8567

8668
// Use the crypto library for proper SHA-1 hashing

0 commit comments

Comments
 (0)