socket-function 0.8.33 → 0.8.35

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/SocketFunction.ts CHANGED
@@ -28,7 +28,6 @@ export class SocketFunction {
28
28
  type: "gzip";
29
29
  };
30
30
  public static httpETagCache = false;
31
- public static rejectUnauthorized = true;
32
31
 
33
32
  public static register<
34
33
  ClassInstance extends object,
@@ -129,11 +128,12 @@ export class SocketFunction {
129
128
  }
130
129
 
131
130
  public static mountedNodeId: string = "NOTMOUNTED";
132
- public static async mount(config: SocketServerConfig) {
131
+ public static async mount(config: SocketServerConfig & { nodeId: string }) {
133
132
  if (this.mountedNodeId !== "NOTMOUNTED") {
134
133
  throw new Error("SocketFunction already mounted, mounting twice in one thread is not allowed.");
135
134
  }
136
- this.mountedNodeId = await startSocketServer(config);
135
+ this.mountedNodeId = config.nodeId;
136
+ await startSocketServer(config);
137
137
  return this.mountedNodeId;
138
138
  }
139
139
 
@@ -1,11 +1,5 @@
1
1
  module.allowclient = true;
2
2
 
3
- import debugbreak from "debugbreak";
4
- import * as tls from "tls";
5
- import { SenderInterface } from "./src/CallFactory";
6
- import { isNode } from "./src/misc";
7
- import { CertInfo, getNodeIdFromCert } from "./src/nodeAuthentication";
8
- import { getClientNodeId } from "./src/nodeCache";
9
3
  import { getCallObj } from "./src/nodeProxy";
10
4
  import { Args, MaybePromise } from "./src/types";
11
5
 
@@ -52,14 +46,14 @@ export interface SocketFunctionHook<ExposedType extends SocketExposedInterface =
52
46
  (config: HookContext<ExposedType, CallContext>): MaybePromise<void>;
53
47
  }
54
48
  export type HookContext<ExposedType extends SocketExposedInterface = SocketExposedInterface, CallContext extends CallContextType = CallContextType> = {
55
- call: CallType;
49
+ call: FullCallType;
56
50
  context: SocketRegistered<ExposedType, CallContext>["context"];
57
51
  // If the result is overriden, we continue evaluating hooks BUT DO NOT perform the final call
58
52
  overrideResult?: unknown;
59
53
  };
60
54
 
61
55
  export type ClientHookContext<ExposedType extends SocketExposedInterface = SocketExposedInterface, CallContext extends CallContextType = CallContextType> = {
62
- call: CallType;
56
+ call: FullCallType;
63
57
  /** If the calls takes longer than this (for ANY reason), we return with an error.
64
58
  * - Different from reconnectTimeout, which only errors if we lose the connection.
65
59
  */
@@ -101,15 +95,6 @@ export type CallerContextBase = {
101
95
  // a more permanent identity, you must derive it from certInfo yourself.
102
96
  nodeId: string;
103
97
 
104
- /** Gives further info on the node. When we set this, we always make sure it has a verified
105
- * issuer. It may be set by app code, which should make sure the issuer is verified (not
106
- * necessarily by the machine, but just in some sense, 'verified', to secure the common name
107
- * of the cert and prevent anyone from using the same common name as someone else).
108
- * IF set, is directly used to derive nodeId (by nodeAuthentication.ts)
109
- */
110
- certInfo: CertInfo | undefined;
111
- updateCertInfo?: (certInfo: CertInfo, callbackPort: number | undefined) => void;
112
-
113
98
  // The nodeId they contacted. This is useful to determine their intention (otherwise
114
99
  // requests can be redirected to us and would accept them, even though they are being
115
100
  // blatantly MITMed).
@@ -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.35",
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",
@@ -10,11 +10,10 @@
10
10
  "@types/node-forge": "^1.3.1",
11
11
  "@types/ws": "^8.5.3",
12
12
  "cookie": "^0.5.0",
13
- "debugbreak": "^0.6.5",
14
13
  "mobx": "^6.6.2",
15
14
  "node-forge": "https://github.com/sliftist/forge#name",
16
15
  "preact": "^10.10.6",
17
- "typenode": "^4.9.4",
16
+ "typenode": "^4.9.4-a",
18
17
  "ws": "^8.8.0"
19
18
  },
20
19
  "scripts": {
@@ -22,6 +21,7 @@
22
21
  "type": "yarn tsc --noEmit"
23
22
  },
24
23
  "devDependencies": {
24
+ "debugbreak": "^0.6.5",
25
25
  "typedev": "^0.1.0"
26
26
  }
27
27
  }
@@ -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).`);
@@ -1,8 +1,8 @@
1
- import { CallerContext, CallerContextBase, CallType } from "../SocketFunctionTypes";
1
+ import { CallerContext, CallerContextBase, CallType, FullCallType } from "../SocketFunctionTypes";
2
2
  import * as ws from "ws";
3
3
  import { performLocalCall } from "./callManager";
4
4
  import { convertErrorStackToError, formatNumberSuffixed, isNode } from "./misc";
5
- import { createWebsocketFactory, getNodeIdFromCert, getTLSSocket } from "./nodeAuthentication";
5
+ import { createWebsocketFactory, getTLSSocket } from "./nodeAuthentication";
6
6
  import { SocketFunction } from "../SocketFunction";
7
7
  import { gzip } from "zlib";
8
8
  import * as tls from "tls";
@@ -10,7 +10,7 @@ import { getClientNodeId, getNodeIdLocation, registerNodeClient } from "./nodeCa
10
10
 
11
11
  const retryInterval = 2000;
12
12
 
13
- type InternalCallType = CallType & {
13
+ type InternalCallType = FullCallType & {
14
14
  seqNum: number;
15
15
  isReturn: false;
16
16
  compress: boolean;
@@ -77,16 +77,7 @@ export async function createCallFactory(
77
77
 
78
78
  let callerContext: CallerContextBase = {
79
79
  nodeId,
80
- localNodeId,
81
- certInfo: webSocketBase?.socket?.getPeerCertificate(true),
82
- updateCertInfo: (certRaw, port) => {
83
- let nodeId = getNodeIdFromCert(certRaw, port);
84
- if (!nodeId) {
85
- return;
86
- }
87
- callerContext.nodeId = nodeId;
88
- callerContext.certInfo = certRaw;
89
- }
80
+ localNodeId
90
81
  };
91
82
 
92
83
  let callFactory: CallFactory = {
@@ -99,6 +90,7 @@ export async function createCallFactory(
99
90
 
100
91
  let seqNum = nextSeqNum++;
101
92
  let fullCall: InternalCallType = {
93
+ nodeId,
102
94
  isReturn: false,
103
95
  args: call.args,
104
96
  classGuid: call.classGuid,
@@ -1,6 +1,6 @@
1
1
  import http from "http";
2
2
  import tls from "tls";
3
- import { CallerContext, CallType } from "../SocketFunctionTypes";
3
+ import { CallerContext, CallType, FullCallType } from "../SocketFunctionTypes";
4
4
  import { isDataImmutable, performLocalCall } from "./callManager";
5
5
  import { SocketFunction } from "../SocketFunction";
6
6
  import { gzip } from "zlib";
@@ -79,7 +79,6 @@ export async function httpCallHandler(request: http.IncomingMessage, response: h
79
79
 
80
80
  let caller: CallerContext = {
81
81
  nodeId,
82
- certInfo: undefined,
83
82
  localNodeId,
84
83
  };
85
84
 
@@ -112,7 +111,8 @@ export async function httpCallHandler(request: http.IncomingMessage, response: h
112
111
  args = JSON.parse(payload.toString())["args"] as unknown[];
113
112
  }
114
113
 
115
- let call: CallType = {
114
+ let call: FullCallType = {
115
+ nodeId,
116
116
  classGuid,
117
117
  functionName,
118
118
  args,
@@ -1,4 +1,4 @@
1
- import { CallContextType, CallerContext, CallType, ClientHookContext, HookContext, SocketExposedInterface, SocketExposedInterfaceClass, SocketExposedShape, SocketFunctionClientHook, SocketFunctionHook, SocketRegistered } from "../SocketFunctionTypes";
1
+ import { CallContextType, CallerContext, CallType, ClientHookContext, FullCallType, HookContext, SocketExposedInterface, SocketExposedInterfaceClass, SocketExposedShape, SocketFunctionClientHook, SocketFunctionHook, SocketRegistered } from "../SocketFunctionTypes";
2
2
  import { _setSocketContext } from "../SocketFunction";
3
3
 
4
4
  let classes: {
@@ -14,7 +14,7 @@ let globalClientHooks: SocketFunctionClientHook[] = [];
14
14
 
15
15
  export async function performLocalCall(
16
16
  config: {
17
- call: CallType;
17
+ call: FullCallType;
18
18
  caller: CallerContext;
19
19
  }
20
20
  ): Promise<unknown> {
@@ -92,7 +92,7 @@ export function unregisterGlobalClientHook(hook: SocketFunctionClientHook) {
92
92
  }
93
93
 
94
94
  export async function runClientHooks(
95
- callType: CallType,
95
+ callType: FullCallType,
96
96
  hooks: SocketExposedShape[""],
97
97
  ): Promise<ClientHookContext> {
98
98
  let context: ClientHookContext = { call: callType };
@@ -106,7 +106,7 @@ export async function runClientHooks(
106
106
  }
107
107
 
108
108
  async function runServerHooks(
109
- callType: CallType,
109
+ callType: FullCallType,
110
110
  context: SocketRegistered["context"],
111
111
  hooks: SocketExposedShape[""],
112
112
  ): Promise<HookContext> {
package/src/certStore.ts CHANGED
@@ -1,50 +1,24 @@
1
- import * as os from "os";
2
- import * as fs from "fs/promises";
3
- import * as fsSync from "fs";
4
- import * as child_process from "child_process";
5
1
  import * as tls from "tls";
6
- import { SocketFunction } from "../SocketFunction";
7
- import { isNode, isNodeTrue, sha256Hash } from "./misc";
8
- import { lazy } from "./caching";
9
2
 
10
3
  let trustedCerts = new Set<string>();
11
- let loadedTrustedCerts = false;
12
4
  let watchCallbacks = new Set<(certs: string[]) => void>();
13
5
 
14
- let storePath = isNodeTrue() && process.argv[1].replaceAll("\\", "/").split("/").slice(0, -1).join("/") + "/certstore/";
15
- if (isNode()) {
16
- if (!fsSync.existsSync(storePath)) {
17
- fsSync.mkdirSync(storePath);
18
- }
19
- }
20
-
21
6
  /** Must be populated before the server starts */
22
- export async function trustUserCertificate(cert: string) {
7
+ export function trustCertificate(cert: string | Buffer) {
8
+ cert = cert.toString();
23
9
  if (trustedCerts.has(cert)) return;
24
10
  trustedCerts.add(cert);
25
- await fs.writeFile(storePath + sha256Hash(Buffer.from(cert)) + ".cer", cert);
26
- let certs = getTrustedUserCertificates();
11
+ let certs = getTrustedCertificates();
27
12
  for (let callback of watchCallbacks) {
28
13
  callback(certs);
29
14
  }
30
15
  }
31
- export const loadTrustedUserCertificates = lazy(async () => {
32
- let files = await fs.readdir(storePath);
33
- for (let file of files) {
34
- let cert = await fs.readFile(storePath + file, "utf8");
35
- trustedCerts.add(cert);
36
- }
37
- loadedTrustedCerts = true;
38
- });
39
- export function getTrustedUserCertificates(): string[] {
40
- if (!loadedTrustedCerts) {
41
- throw new Error("Must call loadTrustedUserCertificates (and await it) before calling getTrustedUserCertificates");
42
- }
43
- return Array.from(trustedCerts);
16
+ export function getTrustedCertificates(): string[] {
17
+ return tls.rootCertificates.concat(Array.from(trustedCerts));
44
18
  }
45
19
 
46
- export function watchUserCertificates(callback: (certs: string[]) => void) {
20
+ export function watchTrustedCertificates(callback: (certs: string[]) => void) {
47
21
  watchCallbacks.add(callback);
48
- callback(getTrustedUserCertificates());
22
+ callback(getTrustedCertificates());
49
23
  return () => watchCallbacks.delete(callback);
50
24
  }
@@ -1,88 +1,15 @@
1
1
  import ws from "ws";
2
2
  import tls from "tls";
3
- import net from "net";
4
- import { getAppFolder } from "./storagePath";
5
- import fs from "fs";
6
- import child_process from "child_process";
7
- import { cacheWeak, lazy } from "./caching";
8
- import https from "https";
9
- import debugbreak from "debugbreak";
10
- import crypto from "crypto";
11
- import { isNode, sha256Hash } from "./misc";
12
- import { getArgs } from "./args";
3
+ import { isNode } from "./misc";
13
4
  import { SenderInterface } from "./CallFactory";
14
- import { SocketFunction } from "../SocketFunction";
15
- import { getTrustedUserCertificates } from "./certStore";
16
- import { getClientNodeId, getNodeId, getNodeIdLocation } from "./nodeCache";
5
+ import { getTrustedCertificates } from "./certStore";
6
+ import { getNodeIdLocation } from "./nodeCache";
17
7
 
18
- export type CertInfo = { raw: Buffer | string; issuerCertificate: { raw: Buffer | string } };
19
-
20
- let certKeyPairOverride: { key: Buffer; cert: Buffer } | undefined;
21
- export function getCertKeyPair(): { key: Buffer; cert: Buffer } {
22
- if (certKeyPairOverride) return certKeyPairOverride;
23
- return getCertKeyPairBase();
24
- }
25
- const getCertKeyPairBase = lazy((): { key: Buffer; cert: Buffer } => {
26
- // https://nodejs.org/en/knowledge/HTTP/servers/how-to-create-a-HTTPS-server/
27
-
28
- let folder = getAppFolder();
29
- let identityPrefix = getArgs().identity || "";
30
- let keyPath = folder + identityPrefix + "key.pem";
31
- let certPath = folder + identityPrefix + "cert.pem";
32
- if (!fs.existsSync(keyPath) || !fs.existsSync(certPath)) {
33
- child_process.execSync(`openssl genrsa -out "${keyPath}"`);
34
- child_process.execSync(`openssl req -new -key "${keyPath}" -out csr.pem -subj "/CN=notused"`);
35
- child_process.execSync(`openssl x509 -req -days 9999 -in csr.pem -signkey "${keyPath}" -out "${certPath}"`);
36
- fs.rmSync("csr.pem");
37
- }
38
-
39
- let key = fs.readFileSync(keyPath);
40
- let cert = fs.readFileSync(certPath);
41
- return { key, cert };
42
- });
43
-
44
- export function overrideCertKeyPair<T>(certKey: { key: Buffer; cert: Buffer; }, code: () => T): T {
45
- if (!isNode()) {
46
- throw new Error(`Cannot override cert/key pair in browser`);
47
- }
48
- let prevOverride = certKeyPairOverride;
49
- certKeyPairOverride = certKey;
50
- try {
51
- return code();
52
- } finally {
53
- certKeyPairOverride = prevOverride;
54
- }
55
- }
56
8
 
57
9
  export function getTLSSocket(webSocket: ws.WebSocket) {
58
10
  return (webSocket as any)._socket as tls.TLSSocket;
59
11
  }
60
12
 
61
- export async function getOwnNodeId() {
62
- if (!isNode()) {
63
- throw new Error(`Clientside nodeIds are not exposed to the client`);
64
- }
65
-
66
- // This is BASICALLY just sha256Hash(getCertKeyPari().cert), however... I'm not 100% the format
67
- // is the same, we would have to verify it. It isn't that important, other nodes know our nodeId,
68
- // and clients don't really have a reason to use this anyway (they can't verify it, they can only
69
- // really verify with a location).
70
- throw new Error(`TODO: Implement getOwnNodeId`);
71
- }
72
-
73
- export function getNodeIdFromCert(certRaw: { raw: Buffer | string } | undefined, callbackPort: number | undefined) {
74
- if (!certRaw?.raw) return undefined;
75
- let cert = new crypto.X509Certificate(certRaw.raw);
76
- if (!callbackPort) {
77
- return getClientNodeId(cert.subject);
78
- }
79
- let subject = cert.subject;
80
- if (subject.startsWith("CN=")) {
81
- subject = subject.slice("CN=".length);
82
- }
83
- return getNodeId(subject, callbackPort);
84
- }
85
-
86
13
  /** NOTE: We create a factory, which embeds the key/cert information. Otherwise retries might use
87
14
  * a different key/cert context.
88
15
  */
@@ -98,8 +25,6 @@ export function createWebsocketFactory(): (nodeId: string) => SenderInterface {
98
25
  return new WebSocket(`wss://${address}:${port}`);
99
26
  };
100
27
  } else {
101
- let { key, cert } = getCertKeyPair();
102
- let rejectUnauthorized = SocketFunction.rejectUnauthorized;
103
28
  return (nodeId: string) => {
104
29
  let location = getNodeIdLocation(nodeId);
105
30
  if (!location) throw new Error(`Cannot connect to ${nodeId}, no address known`);
@@ -107,10 +32,7 @@ export function createWebsocketFactory(): (nodeId: string) => SenderInterface {
107
32
 
108
33
  console.log(`Connecting to ${address}:${port}`);
109
34
  let webSocket = new ws.WebSocket(`wss://${address}:${port}`, {
110
- cert,
111
- key,
112
- rejectUnauthorized,
113
- ca: tls.rootCertificates.concat(getTrustedUserCertificates()),
35
+ ca: tls.rootCertificates.concat(getTrustedCertificates()),
114
36
  });
115
37
  let result = Object.assign(webSocket, { socket: undefined as tls.TLSSocket | undefined });
116
38
  webSocket.once("upgrade", e => {
@@ -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
+ }
@@ -3,97 +3,106 @@ import http from "http";
3
3
  import net from "net";
4
4
  import tls from "tls";
5
5
  import * as ws from "ws";
6
- import { getCertKeyPair, getNodeIdFromCert } from "./nodeAuthentication";
7
6
  import { getNodeIdsFromRequest, httpCallHandler } from "./callHTTPHandler";
8
7
  import { SocketFunction } from "../SocketFunction";
9
- import { getTrustedUserCertificates, loadTrustedUserCertificates, watchUserCertificates } from "./certStore";
8
+ import { getTrustedCertificates, watchTrustedCertificates } from "./certStore";
10
9
  import { createCallFactory } from "./CallFactory";
10
+ import { parseSNIExtension, parseTLSHello, SNIType } from "./tlsParsing";
11
+ import debugbreak from "debugbreak";
11
12
 
12
13
  export type SocketServerConfig = (
13
- {
14
+ https.ServerOptions & {
15
+ nodeId?: string;
16
+
17
+ key: string | Buffer;
18
+ cert: string | Buffer;
19
+
14
20
  port: number;
21
+
15
22
  // public sets ip to "0.0.0.0", otherwise it defaults to "127.0.0.1", which
16
23
  // causes the server to only accept local connections.
17
24
  public?: boolean;
18
25
  ip?: string;
19
- } & (
20
- https.ServerOptions
21
- )
26
+
27
+ /** If the SNI matches this domain, we use a different key/cert. */
28
+ SNICerts?: {
29
+ [domain: string]: https.ServerOptions;
30
+ };
31
+ }
22
32
  );
23
33
 
24
34
  export async function startSocketServer(
25
35
  config: SocketServerConfig
26
- ): Promise<string> {
27
- let isSecure = "cert" in config || "key" in config || "pfx" in config;
28
- if (!isSecure) {
29
- let { key, cert } = getCertKeyPair();
30
- config.key = key;
31
- config.cert = cert;
32
- }
33
-
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
- };
36
+ ): Promise<void> {
43
37
 
44
- let httpsServer = https.createServer(options);
45
- watchUserCertificates(() => {
46
- options.ca = tls.rootCertificates.concat(getTrustedUserCertificates());
47
- httpsServer.setSecureContext(options);
38
+ const webSocketServer = new ws.Server({
39
+ noServer: true,
48
40
  });
49
41
 
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");
42
+ function setupHTTPSServer(options: https.ServerOptions) {
43
+ let httpsServer = https.createServer(options);
44
+ watchTrustedCertificates(() => {
45
+ options.ca = getTrustedCertificates();
46
+ httpsServer.setSecureContext(options);
57
47
  });
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
48
 
49
+ httpsServer.on("connection", socket => {
50
+ console.log("Client connection established");
51
+ socket.on("error", e => {
52
+ console.log(`Client socket error ${e.message}`);
53
+ });
54
+ socket.on("close", () => {
55
+ console.log("Client socket closed");
56
+ });
57
+ });
58
+ httpsServer.on("error", e => {
59
+ console.error(`Connection attempt error ${e.message}`);
60
+ });
61
+ httpsServer.on("tlsClientError", e => {
62
+ console.error(`TLS client error ${e.message}`);
63
+ });
66
64
 
67
- httpsServer.on("request", httpCallHandler);
65
+ httpsServer.on("request", httpCallHandler);
68
66
 
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
- });
67
+ httpsServer.on("upgrade", (request, socket, upgradeHead) => {
68
+ socket.on("error", e => {
69
+ console.log(`Client socket error ${e.message}`);
70
+ });
76
71
 
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)}`);
72
+ let originHeader = request.headers["origin"];
73
+ if (originHeader) {
74
+ try {
75
+ let host = new URL("ws://" + request.headers["host"]).hostname;
76
+ let origin = new URL(originHeader).hostname;
77
+ if (host !== origin) {
78
+ throw new Error(`Invalid cross thread request, ${JSON.stringify(host)} !== ${JSON.stringify(origin)}`);
79
+ }
80
+ } catch (e) {
81
+ console.error(e);
82
+ return;
84
83
  }
85
- } catch (e) {
86
- console.error(e);
87
- return;
88
84
  }
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}`);
85
+ webSocketServer.handleUpgrade(request, socket, upgradeHead, (ws) => {
86
+ const { nodeId, localNodeId } = getNodeIdsFromRequest(request);
87
+ createCallFactory(ws, nodeId, localNodeId).catch(e => {
88
+ console.error(`Error in creating call factory, ${e.stack}`);
89
+ });
94
90
  });
95
91
  });
96
- });
92
+ return httpsServer;
93
+ }
94
+
95
+ // TODO: Only allow unauthorized for ip certificates, and then for domains use the domain as the nodeId,
96
+ // so it is easy to read, and consistent.
97
+ let options: https.ServerOptions = {
98
+ ...config,
99
+ };
100
+
101
+ const mainHTTPSServer = setupHTTPSServer(options);
102
+ let sniServers = new Map<string, https.Server>();
103
+ for (let [domain, obj] of Object.entries(config.SNICerts || {})) {
104
+ sniServers.set(domain, setupHTTPSServer(obj));
105
+ }
97
106
 
98
107
  let httpServer = http.createServer({}, async function (req, res) {
99
108
  let url = new URL("http://" + req.headers.host + req.url);
@@ -114,7 +123,15 @@ export async function startSocketServer(
114
123
  socket.once("data", buffer => {
115
124
  // All HTTPS requests start with 22, and no HTTP requests start with 22,
116
125
  // so we just need to read the first byte.
117
- let server = buffer[0] === 22 ? httpsServer : httpServer;
126
+
127
+ let server: https.Server | http.Server;
128
+ if (buffer[0] !== 22) {
129
+ server = httpServer;
130
+ } else {
131
+ let data = parseTLSHello(buffer);
132
+ let sni = data.extensions.filter(x => x.type === SNIType).flatMap(x => parseSNIExtension(x.data))[0];
133
+ server = sniServers.get(sni) || mainHTTPSServer;
134
+ }
118
135
 
119
136
  // NOTE: Messages aren't dequeued until the current handler finishes, so we don't need to pause the socket or anything.
120
137
  server.emit("connection", socket);
@@ -148,11 +165,5 @@ export async function startSocketServer(
148
165
 
149
166
  let port = (realServer.address() as net.AddressInfo).port;
150
167
 
151
- console.log(`Started Listening on ${host}:${port}`);
152
-
153
- let serverNodeId = getNodeIdFromCert({ raw: config.cert as Buffer | string }, port);
154
- if (!serverNodeId) {
155
- throw new Error(`Something is wrong with our cert, we don't have a nodeId?`);
156
- }
157
- return serverNodeId;
168
+ console.log(`Started Listening on ${config.nodeId || host}:${port}`);
158
169
  }