socket-function 0.8.34 → 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).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "socket-function",
3
- "version": "0.8.34",
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,7 +10,6 @@
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",
@@ -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
  }
@@ -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 => {
@@ -3,16 +3,20 @@ 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";
11
10
  import { parseSNIExtension, parseTLSHello, SNIType } from "./tlsParsing";
12
11
  import debugbreak from "debugbreak";
13
12
 
14
13
  export type SocketServerConfig = (
15
14
  https.ServerOptions & {
15
+ nodeId?: string;
16
+
17
+ key: string | Buffer;
18
+ cert: string | Buffer;
19
+
16
20
  port: number;
17
21
 
18
22
  // public sets ip to "0.0.0.0", otherwise it defaults to "127.0.0.1", which
@@ -20,9 +24,7 @@ export type SocketServerConfig = (
20
24
  public?: boolean;
21
25
  ip?: string;
22
26
 
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
- */
27
+ /** If the SNI matches this domain, we use a different key/cert. */
26
28
  SNICerts?: {
27
29
  [domain: string]: https.ServerOptions;
28
30
  };
@@ -31,25 +33,16 @@ export type SocketServerConfig = (
31
33
 
32
34
  export async function startSocketServer(
33
35
  config: SocketServerConfig
34
- ): Promise<string> {
35
- let isSecure = "cert" in config || "key" in config || "pfx" in config;
36
- if (!isSecure) {
37
- let { key, cert } = getCertKeyPair();
38
- config.key = key;
39
- config.cert = cert;
40
- }
41
-
36
+ ): Promise<void> {
42
37
 
43
38
  const webSocketServer = new ws.Server({
44
39
  noServer: true,
45
40
  });
46
41
 
47
- await loadTrustedUserCertificates();
48
-
49
42
  function setupHTTPSServer(options: https.ServerOptions) {
50
43
  let httpsServer = https.createServer(options);
51
- watchUserCertificates(() => {
52
- options.ca = tls.rootCertificates.concat(getTrustedUserCertificates());
44
+ watchTrustedCertificates(() => {
45
+ options.ca = getTrustedCertificates();
53
46
  httpsServer.setSecureContext(options);
54
47
  });
55
48
 
@@ -102,8 +95,6 @@ export async function startSocketServer(
102
95
  // TODO: Only allow unauthorized for ip certificates, and then for domains use the domain as the nodeId,
103
96
  // so it is easy to read, and consistent.
104
97
  let options: https.ServerOptions = {
105
- rejectUnauthorized: SocketFunction.rejectUnauthorized,
106
- requestCert: true,
107
98
  ...config,
108
99
  };
109
100
 
@@ -137,8 +128,6 @@ export async function startSocketServer(
137
128
  if (buffer[0] !== 22) {
138
129
  server = httpServer;
139
130
  } else {
140
- debugbreak(1);
141
- debugger;
142
131
  let data = parseTLSHello(buffer);
143
132
  let sni = data.extensions.filter(x => x.type === SNIType).flatMap(x => parseSNIExtension(x.data))[0];
144
133
  server = sniServers.get(sni) || mainHTTPSServer;
@@ -176,11 +165,5 @@ export async function startSocketServer(
176
165
 
177
166
  let port = (realServer.address() as net.AddressInfo).port;
178
167
 
179
- console.log(`Started Listening on ${host}:${port}`);
180
-
181
- let serverNodeId = getNodeIdFromCert({ raw: config.cert as Buffer | string }, port);
182
- if (!serverNodeId) {
183
- throw new Error(`Something is wrong with our cert, we don't have a nodeId?`);
184
- }
185
- return serverNodeId;
168
+ console.log(`Started Listening on ${config.nodeId || host}:${port}`);
186
169
  }