sliftutils 1.4.88 → 1.5.0

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/index.d.ts CHANGED
@@ -72,7 +72,7 @@ declare module "sliftutils/misc/https/certs" {
72
72
  import * as forge from "node-forge";
73
73
  import { MaybePromise } from "socket-function/src/types";
74
74
  export declare const CA_NOT_FOUND_ERROR = "18aa7318-f88f-4d2d-b41f-3daf4a433827";
75
- export declare const identityStorageKey = "machineCA_10";
75
+ export declare const identityStorageKey = "machineCA_11";
76
76
  export type IdentityStorageType = {
77
77
  domain: string;
78
78
  certB64: string;
@@ -99,7 +99,7 @@ declare module "sliftutils/misc/https/certs" {
99
99
  export declare const sign: (keyPair: {
100
100
  key: string | Buffer;
101
101
  }, data: unknown) => string;
102
- export declare function verify(cert: string, signature: string, data: unknown): boolean;
102
+ export declare function verify(cert: string, signature: string, data: unknown): void;
103
103
  export declare function validateCACert(domain: string, cert: string | Buffer): void;
104
104
  export declare function validateCertificate(domain: string, cert: Buffer | string, issuerCert: Buffer | string): void;
105
105
  export declare function generateKeyPair(): forge.pki.rsa.KeyPair;
@@ -108,21 +108,22 @@ declare module "sliftutils/misc/https/certs" {
108
108
  export declare function createCertFromCA(config: {
109
109
  CAKeyPair: X509KeyPair;
110
110
  }): X509KeyPair;
111
- export declare function getMachineId(domainName: string): string;
111
+ export declare function getMachineId(domainNameOrNodeId: string, domain: string): string;
112
112
  export type NodeIdParts = {
113
113
  threadId: string;
114
114
  machineId: string;
115
115
  domain: string;
116
116
  port: number;
117
117
  };
118
- export declare function decodeNodeId(nodeId: string): NodeIdParts | undefined;
119
- export declare function decodeNodeIdAssert(nodeId: string): NodeIdParts;
118
+ export declare function decodeNodeId(nodeId: string, domain: string, allowMissingThreadId?: "allowMissingThreadId"): NodeIdParts | undefined;
119
+ export declare function decodeNodeIdAssert(nodeId: string, domain: string, allowMissingThreadId?: "allowMissingThreadId"): NodeIdParts;
120
120
  export declare function encodeNodeId(parts: NodeIdParts): string;
121
121
  export declare function setIdentityCARaw(domain: string, json: string): Promise<void>;
122
122
  export declare function loadIdentityCA(domain: string): Promise<void>;
123
123
  export declare function getIdentityCA(domain: string): X509KeyPair;
124
124
  export declare function getIdentityCAPromise(domain: string): MaybePromise<X509KeyPair>;
125
125
  export declare function getOwnMachineId(domain: string): string;
126
+ export declare function getOwnThreadId(domain: string): string;
126
127
  /** Part of the machineId comes from the publicKey, so we can use it to verify */
127
128
  export declare function verifyMachineIdForPublicKey(config: {
128
129
  machineId: string;
@@ -291,6 +292,11 @@ declare module "sliftutils/misc/random" {
291
292
 
292
293
  }
293
294
 
295
+ declare module "sliftutils/misc/strings" {
296
+ export declare function ellipsize(text: string, maxLength: number): string;
297
+
298
+ }
299
+
294
300
  declare module "sliftutils/misc/types" {
295
301
  export declare function isDefined<T>(value: T | undefined | null): value is T;
296
302
  export declare function freezeObject(value: unknown): unknown;
@@ -5,7 +5,7 @@
5
5
  import * as forge from "node-forge";
6
6
  import { MaybePromise } from "socket-function/src/types";
7
7
  export declare const CA_NOT_FOUND_ERROR = "18aa7318-f88f-4d2d-b41f-3daf4a433827";
8
- export declare const identityStorageKey = "machineCA_10";
8
+ export declare const identityStorageKey = "machineCA_11";
9
9
  export type IdentityStorageType = {
10
10
  domain: string;
11
11
  certB64: string;
@@ -32,7 +32,7 @@ export declare function getPublicIdentifier(PEMorDER: string | Buffer): Buffer;
32
32
  export declare const sign: (keyPair: {
33
33
  key: string | Buffer;
34
34
  }, data: unknown) => string;
35
- export declare function verify(cert: string, signature: string, data: unknown): boolean;
35
+ export declare function verify(cert: string, signature: string, data: unknown): void;
36
36
  export declare function validateCACert(domain: string, cert: string | Buffer): void;
37
37
  export declare function validateCertificate(domain: string, cert: Buffer | string, issuerCert: Buffer | string): void;
38
38
  export declare function generateKeyPair(): forge.pki.rsa.KeyPair;
@@ -41,21 +41,22 @@ export declare function generateTestCA(domain: string): X509KeyPair;
41
41
  export declare function createCertFromCA(config: {
42
42
  CAKeyPair: X509KeyPair;
43
43
  }): X509KeyPair;
44
- export declare function getMachineId(domainName: string): string;
44
+ export declare function getMachineId(domainNameOrNodeId: string, domain: string): string;
45
45
  export type NodeIdParts = {
46
46
  threadId: string;
47
47
  machineId: string;
48
48
  domain: string;
49
49
  port: number;
50
50
  };
51
- export declare function decodeNodeId(nodeId: string): NodeIdParts | undefined;
52
- export declare function decodeNodeIdAssert(nodeId: string): NodeIdParts;
51
+ export declare function decodeNodeId(nodeId: string, domain: string, allowMissingThreadId?: "allowMissingThreadId"): NodeIdParts | undefined;
52
+ export declare function decodeNodeIdAssert(nodeId: string, domain: string, allowMissingThreadId?: "allowMissingThreadId"): NodeIdParts;
53
53
  export declare function encodeNodeId(parts: NodeIdParts): string;
54
54
  export declare function setIdentityCARaw(domain: string, json: string): Promise<void>;
55
55
  export declare function loadIdentityCA(domain: string): Promise<void>;
56
56
  export declare function getIdentityCA(domain: string): X509KeyPair;
57
57
  export declare function getIdentityCAPromise(domain: string): MaybePromise<X509KeyPair>;
58
58
  export declare function getOwnMachineId(domain: string): string;
59
+ export declare function getOwnThreadId(domain: string): string;
59
60
  /** Part of the machineId comes from the publicKey, so we can use it to verify */
60
61
  export declare function verifyMachineIdForPublicKey(config: {
61
62
  machineId: string;
@@ -19,6 +19,7 @@ import { MaybePromise } from "socket-function/src/types";
19
19
  import { SocketFunction } from "socket-function/SocketFunction";
20
20
  import { resetAllNodeCallFactories } from "socket-function/src/nodeCache";
21
21
  import { getKeyStore } from "./persistentLocalStorage";
22
+ import { ellipsize } from "../strings";
22
23
 
23
24
  setFlag(require, "node-forge", "allowclient", true);
24
25
  setFlag(require, "js-sha256", "allowclient", true);
@@ -27,7 +28,7 @@ const timeInDay = 1000 * 60 * 60 * 24;
27
28
 
28
29
  export const CA_NOT_FOUND_ERROR = "18aa7318-f88f-4d2d-b41f-3daf4a433827";
29
30
 
30
- export const identityStorageKey = "machineCA_10";
31
+ export const identityStorageKey = "machineCA_11";
31
32
  export type IdentityStorageType = { domain: string; certB64: string; keyB64: string };
32
33
 
33
34
  function getIdentityStore(domain: string) {
@@ -99,21 +100,17 @@ export function createX509(
99
100
  //{ type: 7, ip: "127.0.0.1" }
100
101
  ]
101
102
  },
102
- // NOTE: nameConstraints are supported with our branch. But... chrome doesn't support them, so there's no point in using them.
103
+ // NOTE: nameConstraints require our forked node-forge:
103
104
  // "node-forge": "https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6",
104
- // {
105
- // name: "nameConstraints",
106
- // permittedSubtrees: [
107
- // // Chrome doesn't respect nameConstraints per https://bugs.chromium.org/p/chromium/issues/detail?id=1072083,
108
- // // as the spec decided that "free to process or ignore such information" (when present in self
109
- // // signed certificates), and therefore the chrome implementation decided "The first order is to behave predictably",
110
- // // so... they're not going to support it, because why have a feature in one place, if it isn't
111
- // // on android as well... ugh...
112
- // // Works fine on Edge though
113
- // { type: 2, value: forge.util.encodeUtf8(domain) },
114
- // { type: 2, value: forge.util.encodeUtf8(localHostDomain) },
115
- // ]
116
- // },
105
+ // Chrome ignores them (https://bugs.chromium.org/p/chromium/issues/detail?id=1072083),
106
+ // but our own validation (validateCACert/validateCertificate) enforces them.
107
+ {
108
+ name: "nameConstraints",
109
+ permittedSubtrees: [
110
+ { type: 2, value: forge.util.encodeUtf8(domain) },
111
+ { type: 2, value: forge.util.encodeUtf8(localHostDomain) },
112
+ ]
113
+ },
117
114
  ]);
118
115
  certObj.setExtensions(extensions);
119
116
 
@@ -191,7 +188,9 @@ export const sign = measureWrap(function sign(keyPair: { key: string | Buffer },
191
188
 
192
189
  export function verify(cert: string, signature: string, data: unknown) {
193
190
  let certObj = parseCert(cert);
194
- return (certObj.publicKey as forge.pki.rsa.PublicKey).verify(JSON.stringify(data), signature);
191
+ if (!(certObj.publicKey as forge.pki.rsa.PublicKey).verify(JSON.stringify(data), signature)) {
192
+ throw new Error(`Signature verification failed. Signature: ${JSON.stringify(signature)} | Data: ${ellipsize(JSON.stringify(data), 1024)}`);
193
+ }
195
194
  }
196
195
 
197
196
  function normalizeCertToPEM(PEMorDER: string | Buffer): string {
@@ -211,7 +210,7 @@ function getDomainPartFromPublicKey(publicKey: { publicKeyBytes: Buffer } | forg
211
210
  } else {
212
211
  bytes = Buffer.from(new Uint32Array((publicKey as any).n.data).buffer);
213
212
  }
214
- return "b" + sha265.sha256(Buffer.from(bytes)).slice(0, 16).replaceAll("+", "-").replaceAll("/", "_");
213
+ return "b" + Buffer.from(sha265.sha256.array(Buffer.from(bytes))).toString("base64").slice(0, 16).replaceAll("+", "-").replaceAll("/", "_");
215
214
  }
216
215
 
217
216
  export function validateCACert(domain: string, cert: string | Buffer) {
@@ -440,8 +439,8 @@ export function createCertFromCA(config: {
440
439
  });
441
440
  }
442
441
 
443
- export function getMachineId(domainName: string) {
444
- return domainName.split(".").slice(-3).join(".");
442
+ export function getMachineId(domainNameOrNodeId: string, domain: string) {
443
+ return decodeNodeIdAssert(domainNameOrNodeId, domain, "allowMissingThreadId").machineId;
445
444
  }
446
445
 
447
446
  export type NodeIdParts = {
@@ -450,13 +449,18 @@ export type NodeIdParts = {
450
449
  domain: string;
451
450
  port: number;
452
451
  };
453
- export function decodeNodeId(nodeId: string): NodeIdParts | undefined {
452
+ export function decodeNodeId(nodeId: string, domain: string, allowMissingThreadId?: "allowMissingThreadId"): NodeIdParts | undefined {
454
453
  let locationObj = getNodeIdLocation(nodeId);
455
454
  if (!locationObj) {
456
455
  return undefined;
457
456
  }
458
457
  let parts = locationObj.address.split(".");
459
- if (nodeId.startsWith("127-0-0-1.") && parts.length === 3) {
458
+ // NOTE: We have to only allow localhost domains on our own domain, as the underlying domain
459
+ // gets stripped when we're looking at the machineId. So if we allowed localhost domains on
460
+ // other domains, a server could trick us into connecting to it, and then once the connection
461
+ // is established, it could talk back and we would think it has a localhost machineId, which
462
+ // is implicitly trusted, which would then give it access to everything.
463
+ if (locationObj.address === `127-0-0-1.${domain}` && nodeId.includes(":")) {
460
464
  return {
461
465
  threadId: "",
462
466
  machineId: parts.at(-3) || "",
@@ -464,7 +468,8 @@ export function decodeNodeId(nodeId: string): NodeIdParts | undefined {
464
468
  port: locationObj.port,
465
469
  };
466
470
  }
467
- if (parts.length < 4) {
471
+ let isValid = parts.length >= 4 || allowMissingThreadId && parts.length === 3;
472
+ if (!isValid) {
468
473
  return undefined;
469
474
  }
470
475
  return {
@@ -474,10 +479,10 @@ export function decodeNodeId(nodeId: string): NodeIdParts | undefined {
474
479
  port: locationObj.port,
475
480
  };
476
481
  }
477
- export function decodeNodeIdAssert(nodeId: string): NodeIdParts {
478
- let result = decodeNodeId(nodeId);
482
+ export function decodeNodeIdAssert(nodeId: string, domain: string, allowMissingThreadId?: "allowMissingThreadId"): NodeIdParts {
483
+ let result = decodeNodeId(nodeId, domain, allowMissingThreadId);
479
484
  if (!result) {
480
- throw new Error(`Invalid nodeId: ${nodeId}`);
485
+ throw new Error(`Invalid nodeId: ${JSON.stringify(nodeId)}`);
481
486
  }
482
487
  return result;
483
488
  }
@@ -523,7 +528,10 @@ export function getIdentityCAPromise(domain: string): MaybePromise<X509KeyPair>
523
528
 
524
529
 
525
530
  export function getOwnMachineId(domain: string) {
526
- return getMachineId(getIdentityCA(domain).domain);
531
+ return getMachineId(getIdentityCA(domain).domain, domain);
532
+ }
533
+ export function getOwnThreadId(domain: string) {
534
+ return decodeNodeIdAssert(getThreadKeyCert(domain).domain, domain).threadId;
527
535
  }
528
536
 
529
537
  /** Part of the machineId comes from the publicKey, so we can use it to verify */
@@ -533,7 +541,8 @@ export function verifyMachineIdForPublicKey(config: {
533
541
  }): boolean {
534
542
  let { machineId, publicKey } = config;
535
543
  let domainPart = getDomainPartFromPublicKey(publicKey);
536
- return machineId.split(".").at(-3) === domainPart;
544
+ // machineId is the bare key-hash label, but also accept legacy "hash.domain.tld" forms
545
+ return machineId.split(".")[0] === domainPart;
537
546
  }
538
547
 
539
548
  // NOTE: We don't have a cache per CA, as... the CA should be set first
@@ -0,0 +1 @@
1
+ export declare function ellipsize(text: string, maxLength: number): string;
@@ -0,0 +1,8 @@
1
+ module.allowclient = true;
2
+
3
+ export function ellipsize(text: string, maxLength: number): string {
4
+ if (text.length <= maxLength) {
5
+ return text;
6
+ }
7
+ return text.slice(0, maxLength - 3) + "...";
8
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sliftutils",
3
- "version": "1.4.88",
3
+ "version": "1.5.0",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "files": [