socket-function 0.8.32 → 0.8.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/hot/HotReloadController.ts +2 -0
- package/index.d.ts +1 -0
- package/package.json +2 -2
- package/require/RequireController.ts +1 -1
- package/src/tlsParsing.ts +97 -0
- package/src/webSocketServer.ts +84 -56
package/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference path="../typenode/index.d.ts" />
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "socket-function",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.34",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"note1": "note on node-forge fork, see https://github.com/digitalbazaar/forge/issues/744 for details",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"mobx": "^6.6.2",
|
|
15
15
|
"node-forge": "https://github.com/sliftist/forge#name",
|
|
16
16
|
"preact": "^10.10.6",
|
|
17
|
-
"typenode": "^4.
|
|
17
|
+
"typenode": "^4.9.4-a",
|
|
18
18
|
"ws": "^8.8.0"
|
|
19
19
|
},
|
|
20
20
|
"scripts": {
|
|
@@ -216,7 +216,7 @@ class RequireControllerBase {
|
|
|
216
216
|
//require(rootPath);
|
|
217
217
|
let clientModule = require.cache[resolvedPath];
|
|
218
218
|
if (!clientModule) {
|
|
219
|
-
clientModule = createNotFoundModule(`Module ${pathRequest} (resolved to ${resolvedPath}) was not included serverside
|
|
219
|
+
clientModule = createNotFoundModule(`Module ${pathRequest} (resolved to ${JSON.stringify(resolvedPath)}) was not included serverside. Resolve root ${JSON.stringify(this.rootResolvePath)} (set by call to setRequireBootRequire), resolve search paths: ${JSON.stringify(searchPaths)})}`);
|
|
220
220
|
}
|
|
221
221
|
if (!clientModule.allowclient) {
|
|
222
222
|
clientModule = createNotFoundModule(`Module ${pathRequest} (resolved to ${resolvedPath}) is not allowed clientside (set module.allowclient in it, or call setFlag when it is imported).`);
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
export function parseTLSHello(buffer: Buffer): {
|
|
2
|
+
extensions: {
|
|
3
|
+
type: number;
|
|
4
|
+
data: Buffer;
|
|
5
|
+
}[];
|
|
6
|
+
} {
|
|
7
|
+
let output: {
|
|
8
|
+
extensions: {
|
|
9
|
+
type: number;
|
|
10
|
+
data: Buffer;
|
|
11
|
+
}[];
|
|
12
|
+
} = {
|
|
13
|
+
extensions: []
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
let pos = 0;
|
|
18
|
+
|
|
19
|
+
function readShort() {
|
|
20
|
+
let high = buffer[pos++];
|
|
21
|
+
let low = buffer[pos++];
|
|
22
|
+
return high * 256 + low;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let type = buffer[pos++];
|
|
26
|
+
let version = readShort();
|
|
27
|
+
|
|
28
|
+
var contentLength = readShort();
|
|
29
|
+
|
|
30
|
+
let clientMessageType = buffer[pos++];
|
|
31
|
+
// High length byte (how would this be used if contentLength is only 2 bytes?)
|
|
32
|
+
pos++;
|
|
33
|
+
let clientMessageLength = readShort();
|
|
34
|
+
|
|
35
|
+
// Client version
|
|
36
|
+
let clientVersion = readShort();
|
|
37
|
+
|
|
38
|
+
// Client random
|
|
39
|
+
pos += 32;
|
|
40
|
+
|
|
41
|
+
// Session id
|
|
42
|
+
let sessionIdLength = buffer[pos++];
|
|
43
|
+
pos += sessionIdLength;
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
let cipherSuiteLength = readShort();
|
|
47
|
+
pos += cipherSuiteLength;
|
|
48
|
+
|
|
49
|
+
let compressionLength = buffer[pos++];
|
|
50
|
+
pos += compressionLength;
|
|
51
|
+
|
|
52
|
+
let extensionsLength = readShort();
|
|
53
|
+
let extensionsEnd = pos + extensionsLength;
|
|
54
|
+
while (pos < extensionsEnd) {
|
|
55
|
+
let extensionType = readShort();
|
|
56
|
+
let length = readShort();
|
|
57
|
+
|
|
58
|
+
output.extensions.push({
|
|
59
|
+
type: extensionType, data: viewSliceBuffer(buffer, pos, length)
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
pos += length;
|
|
63
|
+
}
|
|
64
|
+
} catch { }
|
|
65
|
+
|
|
66
|
+
return output;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const SNIType = 0x0;
|
|
70
|
+
export function parseSNIExtension(data: Buffer): string[] {
|
|
71
|
+
let pos = 0;
|
|
72
|
+
function readShort() {
|
|
73
|
+
let high = data[pos++];
|
|
74
|
+
let low = data[pos++];
|
|
75
|
+
return high * 256 + low;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let snis: string[] = [];
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
while (pos < data.length) {
|
|
82
|
+
let len = readShort();
|
|
83
|
+
let end = pos + len;
|
|
84
|
+
let type = data[pos++];
|
|
85
|
+
let len2 = readShort();
|
|
86
|
+
snis.push(viewSliceBuffer(data, pos, len2).toString());
|
|
87
|
+
pos = end;
|
|
88
|
+
}
|
|
89
|
+
} catch { }
|
|
90
|
+
|
|
91
|
+
return snis;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function viewSliceBuffer(data: Buffer, index: number, count?: number) {
|
|
95
|
+
count = count ?? (data.length - index);
|
|
96
|
+
return Buffer.from(data.buffer, data.byteOffset + index, count);
|
|
97
|
+
}
|
package/src/webSocketServer.ts
CHANGED
|
@@ -8,17 +8,25 @@ import { getNodeIdsFromRequest, httpCallHandler } from "./callHTTPHandler";
|
|
|
8
8
|
import { SocketFunction } from "../SocketFunction";
|
|
9
9
|
import { getTrustedUserCertificates, loadTrustedUserCertificates, watchUserCertificates } from "./certStore";
|
|
10
10
|
import { createCallFactory } from "./CallFactory";
|
|
11
|
+
import { parseSNIExtension, parseTLSHello, SNIType } from "./tlsParsing";
|
|
12
|
+
import debugbreak from "debugbreak";
|
|
11
13
|
|
|
12
14
|
export type SocketServerConfig = (
|
|
13
|
-
{
|
|
15
|
+
https.ServerOptions & {
|
|
14
16
|
port: number;
|
|
17
|
+
|
|
15
18
|
// public sets ip to "0.0.0.0", otherwise it defaults to "127.0.0.1", which
|
|
16
19
|
// causes the server to only accept local connections.
|
|
17
20
|
public?: boolean;
|
|
18
21
|
ip?: string;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
|
|
23
|
+
/** If the SNI matches this domain, we use a different key/cert.
|
|
24
|
+
* - Also requestCert may be specified (otherwise it defaults to true)
|
|
25
|
+
*/
|
|
26
|
+
SNICerts?: {
|
|
27
|
+
[domain: string]: https.ServerOptions;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
22
30
|
);
|
|
23
31
|
|
|
24
32
|
export async function startSocketServer(
|
|
@@ -31,69 +39,79 @@ export async function startSocketServer(
|
|
|
31
39
|
config.cert = cert;
|
|
32
40
|
}
|
|
33
41
|
|
|
34
|
-
await loadTrustedUserCertificates();
|
|
35
|
-
|
|
36
|
-
// TODO: Only allow unauthorized for ip certificates, and then for domains use the domain as the nodeId,
|
|
37
|
-
// so it is easy to read, and consistent.
|
|
38
|
-
let options: https.ServerOptions = {
|
|
39
|
-
...config,
|
|
40
|
-
rejectUnauthorized: SocketFunction.rejectUnauthorized,
|
|
41
|
-
requestCert: true,
|
|
42
|
-
};
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
options.ca = tls.rootCertificates.concat(getTrustedUserCertificates());
|
|
47
|
-
httpsServer.setSecureContext(options);
|
|
43
|
+
const webSocketServer = new ws.Server({
|
|
44
|
+
noServer: true,
|
|
48
45
|
});
|
|
49
46
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
47
|
+
await loadTrustedUserCertificates();
|
|
48
|
+
|
|
49
|
+
function setupHTTPSServer(options: https.ServerOptions) {
|
|
50
|
+
let httpsServer = https.createServer(options);
|
|
51
|
+
watchUserCertificates(() => {
|
|
52
|
+
options.ca = tls.rootCertificates.concat(getTrustedUserCertificates());
|
|
53
|
+
httpsServer.setSecureContext(options);
|
|
57
54
|
});
|
|
58
|
-
});
|
|
59
|
-
httpsServer.on("error", e => {
|
|
60
|
-
console.error(`Connection attempt error ${e.message}`);
|
|
61
|
-
});
|
|
62
|
-
httpsServer.on("tlsClientError", e => {
|
|
63
|
-
console.error(`TLS client error ${e.message}`);
|
|
64
|
-
});
|
|
65
55
|
|
|
56
|
+
httpsServer.on("connection", socket => {
|
|
57
|
+
console.log("Client connection established");
|
|
58
|
+
socket.on("error", e => {
|
|
59
|
+
console.log(`Client socket error ${e.message}`);
|
|
60
|
+
});
|
|
61
|
+
socket.on("close", () => {
|
|
62
|
+
console.log("Client socket closed");
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
httpsServer.on("error", e => {
|
|
66
|
+
console.error(`Connection attempt error ${e.message}`);
|
|
67
|
+
});
|
|
68
|
+
httpsServer.on("tlsClientError", e => {
|
|
69
|
+
console.error(`TLS client error ${e.message}`);
|
|
70
|
+
});
|
|
66
71
|
|
|
67
|
-
|
|
72
|
+
httpsServer.on("request", httpCallHandler);
|
|
68
73
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
socket.on("error", e => {
|
|
74
|
-
console.log(`Client socket error ${e.message}`);
|
|
75
|
-
});
|
|
74
|
+
httpsServer.on("upgrade", (request, socket, upgradeHead) => {
|
|
75
|
+
socket.on("error", e => {
|
|
76
|
+
console.log(`Client socket error ${e.message}`);
|
|
77
|
+
});
|
|
76
78
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
let originHeader = request.headers["origin"];
|
|
80
|
+
if (originHeader) {
|
|
81
|
+
try {
|
|
82
|
+
let host = new URL("ws://" + request.headers["host"]).hostname;
|
|
83
|
+
let origin = new URL(originHeader).hostname;
|
|
84
|
+
if (host !== origin) {
|
|
85
|
+
throw new Error(`Invalid cross thread request, ${JSON.stringify(host)} !== ${JSON.stringify(origin)}`);
|
|
86
|
+
}
|
|
87
|
+
} catch (e) {
|
|
88
|
+
console.error(e);
|
|
89
|
+
return;
|
|
84
90
|
}
|
|
85
|
-
} catch (e) {
|
|
86
|
-
console.error(e);
|
|
87
|
-
return;
|
|
88
91
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
webSocketServer.handleUpgrade(request, socket, upgradeHead, (ws) => {
|
|
93
|
+
const { nodeId, localNodeId } = getNodeIdsFromRequest(request);
|
|
94
|
+
createCallFactory(ws, nodeId, localNodeId).catch(e => {
|
|
95
|
+
console.error(`Error in creating call factory, ${e.stack}`);
|
|
96
|
+
});
|
|
94
97
|
});
|
|
95
98
|
});
|
|
96
|
-
|
|
99
|
+
return httpsServer;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// TODO: Only allow unauthorized for ip certificates, and then for domains use the domain as the nodeId,
|
|
103
|
+
// so it is easy to read, and consistent.
|
|
104
|
+
let options: https.ServerOptions = {
|
|
105
|
+
rejectUnauthorized: SocketFunction.rejectUnauthorized,
|
|
106
|
+
requestCert: true,
|
|
107
|
+
...config,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const mainHTTPSServer = setupHTTPSServer(options);
|
|
111
|
+
let sniServers = new Map<string, https.Server>();
|
|
112
|
+
for (let [domain, obj] of Object.entries(config.SNICerts || {})) {
|
|
113
|
+
sniServers.set(domain, setupHTTPSServer(obj));
|
|
114
|
+
}
|
|
97
115
|
|
|
98
116
|
let httpServer = http.createServer({}, async function (req, res) {
|
|
99
117
|
let url = new URL("http://" + req.headers.host + req.url);
|
|
@@ -114,7 +132,17 @@ export async function startSocketServer(
|
|
|
114
132
|
socket.once("data", buffer => {
|
|
115
133
|
// All HTTPS requests start with 22, and no HTTP requests start with 22,
|
|
116
134
|
// so we just need to read the first byte.
|
|
117
|
-
|
|
135
|
+
|
|
136
|
+
let server: https.Server | http.Server;
|
|
137
|
+
if (buffer[0] !== 22) {
|
|
138
|
+
server = httpServer;
|
|
139
|
+
} else {
|
|
140
|
+
debugbreak(1);
|
|
141
|
+
debugger;
|
|
142
|
+
let data = parseTLSHello(buffer);
|
|
143
|
+
let sni = data.extensions.filter(x => x.type === SNIType).flatMap(x => parseSNIExtension(x.data))[0];
|
|
144
|
+
server = sniServers.get(sni) || mainHTTPSServer;
|
|
145
|
+
}
|
|
118
146
|
|
|
119
147
|
// NOTE: Messages aren't dequeued until the current handler finishes, so we don't need to pause the socket or anything.
|
|
120
148
|
server.emit("connection", socket);
|