socket-function 0.8.33 → 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.
@@ -1,3 +1,5 @@
1
+ /// <reference path="../../typenode/index.d.ts" />
2
+ /// <reference path="../require/RequireController.ts" />
1
3
  module.allowclient = true;
2
4
 
3
5
  import { SocketFunction } from "../SocketFunction";
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.33",
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.9.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
+ }
@@ -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
- https.ServerOptions
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
- let httpsServer = https.createServer(options);
45
- watchUserCertificates(() => {
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
- httpsServer.on("connection", socket => {
51
- console.log("Client connection established");
52
- socket.on("error", e => {
53
- console.log(`Client socket error ${e.message}`);
54
- });
55
- socket.on("close", () => {
56
- console.log("Client socket closed");
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
- httpsServer.on("request", httpCallHandler);
72
+ httpsServer.on("request", httpCallHandler);
68
73
 
69
- const webSocketServer = new ws.Server({
70
- noServer: true,
71
- });
72
- httpsServer.on("upgrade", (request, socket, upgradeHead) => {
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
- let originHeader = request.headers["origin"];
78
- if (originHeader) {
79
- try {
80
- let host = new URL("ws://" + request.headers["host"]).hostname;
81
- let origin = new URL(originHeader).hostname;
82
- if (host !== origin) {
83
- throw new Error(`Invalid cross thread request, ${JSON.stringify(host)} !== ${JSON.stringify(origin)}`);
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
- webSocketServer.handleUpgrade(request, socket, upgradeHead, (ws) => {
91
- const { nodeId, localNodeId } = getNodeIdsFromRequest(request);
92
- createCallFactory(ws, nodeId, localNodeId).catch(e => {
93
- console.error(`Error in creating call factory, ${e.stack}`);
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
- let server = buffer[0] === 22 ? httpsServer : httpServer;
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);