sliftutils 1.5.2 → 1.5.4

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
@@ -70,7 +70,6 @@ declare module "sliftutils/misc/https/certs" {
70
70
  /// <reference types="node" />
71
71
  /// <reference types="node" />
72
72
  import * as forge from "node-forge";
73
- import { MaybePromise } from "socket-function/src/types";
74
73
  export declare const CA_NOT_FOUND_ERROR = "18aa7318-f88f-4d2d-b41f-3daf4a433827";
75
74
  export declare const identityStorageKey = "machineCA_12";
76
75
  export type IdentityStorageType = {
@@ -78,20 +77,11 @@ declare module "sliftutils/misc/https/certs" {
78
77
  certB64: string;
79
78
  keyB64: string;
80
79
  };
81
- export type BrowserIdentityStorageType = {
82
- domain: string;
83
- certB64: string;
84
- privateKey: CryptoKey;
85
- };
86
80
  export interface X509KeyPair {
87
81
  domain: string;
88
82
  cert: Buffer;
89
- key: Buffer | CryptoKey;
83
+ key: Buffer;
90
84
  }
91
- export type NonExtractableKeyPair = {
92
- publicKeyBytes: Buffer;
93
- privateKey: CryptoKey;
94
- };
95
85
  export declare function getCommonName(cert: Buffer | string): string;
96
86
  export declare function createX509(config: {
97
87
  domain: string;
@@ -100,24 +90,23 @@ declare module "sliftutils/misc/https/certs" {
100
90
  keyPair: {
101
91
  publicKey: forge.Ed25519PublicKey;
102
92
  privateKey: forge.Ed25519PrivateKey;
103
- } | forge.pki.KeyPair | NonExtractableKeyPair;
104
- }): MaybePromise<X509KeyPair>;
105
- export declare function generateNonExtractableKeyPair(): Promise<NonExtractableKeyPair>;
93
+ } | forge.pki.KeyPair;
94
+ }): X509KeyPair;
106
95
  export declare function privateKeyToPem(buffer: forge.pki.PrivateKey | forge.Ed25519PrivateKey): string;
107
96
  export declare function parseCert(PEMorDER: string | Buffer): forge.pki.Certificate;
108
97
  export declare function getPublicIdentifier(PEMorDER: string | Buffer): Buffer;
109
98
  export declare const sign: (keyPair: {
110
- key: string | Buffer | CryptoKey;
111
- }, data: unknown) => MaybePromise<string>;
99
+ key: string | Buffer;
100
+ }, data: unknown) => string;
112
101
  export declare function verify(cert: string, signature: string, data: unknown): void;
113
102
  export declare function validateCACert(domain: string, cert: string | Buffer): void;
114
103
  export declare function validateCertificate(domain: string, cert: Buffer | string, issuerCert: Buffer | string): void;
115
104
  export declare function generateKeyPair(): forge.pki.rsa.KeyPair;
116
105
  export declare function generateRSAKeyPair(): forge.pki.rsa.KeyPair;
117
- export declare function generateTestCA(domain: string): MaybePromise<X509KeyPair>;
106
+ export declare function generateTestCA(domain: string): X509KeyPair;
118
107
  export declare function createCertFromCA(config: {
119
108
  CAKeyPair: X509KeyPair;
120
- }): MaybePromise<X509KeyPair>;
109
+ }): X509KeyPair;
121
110
  export declare function getMachineId(domainNameOrNodeId: string, domain: string): string;
122
111
  export type NodeIdParts = {
123
112
  threadId: string;
@@ -131,7 +120,7 @@ declare module "sliftutils/misc/https/certs" {
131
120
  export declare function setIdentityCARaw(domain: string, json: string): Promise<void>;
132
121
  export declare function loadIdentityCA(domain: string): Promise<void>;
133
122
  export declare function getIdentityCA(domain: string): X509KeyPair;
134
- export declare function getIdentityCAPromise(domain: string): MaybePromise<X509KeyPair>;
123
+ export declare function getIdentityCAPromise(domain: string): X509KeyPair;
135
124
  export declare function getOwnMachineId(domain: string): string;
136
125
  export declare function getOwnThreadId(domain: string): string;
137
126
  /** Part of the machineId comes from the publicKey, so we can use it to verify */
@@ -139,7 +128,7 @@ declare module "sliftutils/misc/https/certs" {
139
128
  machineId: string;
140
129
  publicKey: Buffer;
141
130
  }): boolean;
142
- export declare function getThreadKeyCert(domain: string): MaybePromise<X509KeyPair>;
131
+ export declare function getThreadKeyCert(domain: string): X509KeyPair;
143
132
  export declare const createTestBrowserKeyCert: {
144
133
  (): Promise<X509KeyPair>;
145
134
  reset(): void;
@@ -223,21 +212,13 @@ declare module "sliftutils/misc/https/node-forge-ed25519" {
223
212
  static publicKeyToPem(key: Ed25519PublicKey): string;
224
213
  static publicKeyFromPem(pem: string): Ed25519PublicKey;
225
214
  }
226
- namespace pki {
227
- function getTBSCertificate(cert: pki.Certificate): asn1.Asn1;
228
- }
229
215
  }
230
216
  }
231
217
 
232
218
  declare module "sliftutils/misc/https/persistentLocalStorage" {
233
- import { MaybePromise } from "socket-function/src/types";
234
- export declare function getIDBKeyStore<T>(appName: string, key: string): {
235
- get(): Promise<T | undefined>;
236
- set(value: T | undefined): Promise<void>;
237
- };
238
219
  export declare function getKeyStore<T>(appName: string, key: string): {
239
- get(): MaybePromise<T | undefined>;
240
- set(value: T | null): MaybePromise<void>;
220
+ get(): T | undefined;
221
+ set(value: T | null): void;
241
222
  };
242
223
 
243
224
  }
@@ -3,7 +3,6 @@
3
3
  /// <reference types="node" />
4
4
  /// <reference types="node" />
5
5
  import * as forge from "node-forge";
6
- import { MaybePromise } from "socket-function/src/types";
7
6
  export declare const CA_NOT_FOUND_ERROR = "18aa7318-f88f-4d2d-b41f-3daf4a433827";
8
7
  export declare const identityStorageKey = "machineCA_12";
9
8
  export type IdentityStorageType = {
@@ -11,20 +10,11 @@ export type IdentityStorageType = {
11
10
  certB64: string;
12
11
  keyB64: string;
13
12
  };
14
- export type BrowserIdentityStorageType = {
15
- domain: string;
16
- certB64: string;
17
- privateKey: CryptoKey;
18
- };
19
13
  export interface X509KeyPair {
20
14
  domain: string;
21
15
  cert: Buffer;
22
- key: Buffer | CryptoKey;
16
+ key: Buffer;
23
17
  }
24
- export type NonExtractableKeyPair = {
25
- publicKeyBytes: Buffer;
26
- privateKey: CryptoKey;
27
- };
28
18
  export declare function getCommonName(cert: Buffer | string): string;
29
19
  export declare function createX509(config: {
30
20
  domain: string;
@@ -33,24 +23,23 @@ export declare function createX509(config: {
33
23
  keyPair: {
34
24
  publicKey: forge.Ed25519PublicKey;
35
25
  privateKey: forge.Ed25519PrivateKey;
36
- } | forge.pki.KeyPair | NonExtractableKeyPair;
37
- }): MaybePromise<X509KeyPair>;
38
- export declare function generateNonExtractableKeyPair(): Promise<NonExtractableKeyPair>;
26
+ } | forge.pki.KeyPair;
27
+ }): X509KeyPair;
39
28
  export declare function privateKeyToPem(buffer: forge.pki.PrivateKey | forge.Ed25519PrivateKey): string;
40
29
  export declare function parseCert(PEMorDER: string | Buffer): forge.pki.Certificate;
41
30
  export declare function getPublicIdentifier(PEMorDER: string | Buffer): Buffer;
42
31
  export declare const sign: (keyPair: {
43
- key: string | Buffer | CryptoKey;
44
- }, data: unknown) => MaybePromise<string>;
32
+ key: string | Buffer;
33
+ }, data: unknown) => string;
45
34
  export declare function verify(cert: string, signature: string, data: unknown): void;
46
35
  export declare function validateCACert(domain: string, cert: string | Buffer): void;
47
36
  export declare function validateCertificate(domain: string, cert: Buffer | string, issuerCert: Buffer | string): void;
48
37
  export declare function generateKeyPair(): forge.pki.rsa.KeyPair;
49
38
  export declare function generateRSAKeyPair(): forge.pki.rsa.KeyPair;
50
- export declare function generateTestCA(domain: string): MaybePromise<X509KeyPair>;
39
+ export declare function generateTestCA(domain: string): X509KeyPair;
51
40
  export declare function createCertFromCA(config: {
52
41
  CAKeyPair: X509KeyPair;
53
- }): MaybePromise<X509KeyPair>;
42
+ }): X509KeyPair;
54
43
  export declare function getMachineId(domainNameOrNodeId: string, domain: string): string;
55
44
  export type NodeIdParts = {
56
45
  threadId: string;
@@ -64,7 +53,7 @@ export declare function encodeNodeId(parts: NodeIdParts): string;
64
53
  export declare function setIdentityCARaw(domain: string, json: string): Promise<void>;
65
54
  export declare function loadIdentityCA(domain: string): Promise<void>;
66
55
  export declare function getIdentityCA(domain: string): X509KeyPair;
67
- export declare function getIdentityCAPromise(domain: string): MaybePromise<X509KeyPair>;
56
+ export declare function getIdentityCAPromise(domain: string): X509KeyPair;
68
57
  export declare function getOwnMachineId(domain: string): string;
69
58
  export declare function getOwnThreadId(domain: string): string;
70
59
  /** Part of the machineId comes from the publicKey, so we can use it to verify */
@@ -72,7 +61,7 @@ export declare function verifyMachineIdForPublicKey(config: {
72
61
  machineId: string;
73
62
  publicKey: Buffer;
74
63
  }): boolean;
75
- export declare function getThreadKeyCert(domain: string): MaybePromise<X509KeyPair>;
64
+ export declare function getThreadKeyCert(domain: string): X509KeyPair;
76
65
  export declare const createTestBrowserKeyCert: {
77
66
  (): Promise<X509KeyPair>;
78
67
  reset(): void;
@@ -15,10 +15,9 @@ import crypto from "crypto";
15
15
  import { trustCertificate } from "socket-function/src/certStore";
16
16
  import { measureBlock, measureFnc, measureWrap } from "socket-function/src/profiling/measure";
17
17
  import { getNodeIdDomain, getNodeIdDomainMaybeUndefined, getNodeIdLocation } from "socket-function/src/nodeCache";
18
- import { MaybePromise } from "socket-function/src/types";
19
18
  import { SocketFunction } from "socket-function/SocketFunction";
20
19
  import { resetAllNodeCallFactories } from "socket-function/src/nodeCache";
21
- import { getKeyStore, getIDBKeyStore } from "./persistentLocalStorage";
20
+ import { getKeyStore } from "./persistentLocalStorage";
22
21
  import { ellipsize } from "../strings";
23
22
 
24
23
  setFlag(require, "node-forge", "allowclient", true);
@@ -30,20 +29,12 @@ export const CA_NOT_FOUND_ERROR = "18aa7318-f88f-4d2d-b41f-3daf4a433827";
30
29
 
31
30
  export const identityStorageKey = "machineCA_12";
32
31
  export type IdentityStorageType = { domain: string; certB64: string; keyB64: string };
33
- // In the browser the private key is a non-extractable CryptoKey. IndexedDB structured-clones
34
- // it, so it persists, but the raw key bytes can never be read by JavaScript (only used
35
- // via crypto.subtle.sign). This protects against exfiltration of stored keys.
36
- export type BrowserIdentityStorageType = { domain: string; certB64: string; privateKey: CryptoKey };
37
32
 
38
33
  function getIdentityStore(domain: string) {
39
34
  return getKeyStore<IdentityStorageType>(domain, identityStorageKey);
40
35
  }
41
- function getBrowserIdentityStore(domain: string) {
42
- return getIDBKeyStore<BrowserIdentityStorageType>(domain, identityStorageKey);
43
- }
44
36
 
45
- export interface X509KeyPair { domain: string; cert: Buffer; key: Buffer | CryptoKey; }
46
- export type NonExtractableKeyPair = { publicKeyBytes: Buffer; privateKey: CryptoKey };
37
+ export interface X509KeyPair { domain: string; cert: Buffer; key: Buffer; }
47
38
 
48
39
  export function getCommonName(cert: Buffer | string) {
49
40
  let subject = new crypto.X509Certificate(cert).subject;
@@ -61,18 +52,14 @@ export function createX509(
61
52
  keyPair: {
62
53
  publicKey: forge.Ed25519PublicKey;
63
54
  privateKey: forge.Ed25519PrivateKey;
64
- } | forge.pki.KeyPair | NonExtractableKeyPair;
55
+ } | forge.pki.KeyPair;
65
56
  }
66
- ): MaybePromise<X509KeyPair> {
67
- return measureBlock(function createX509(): MaybePromise<X509KeyPair> {
57
+ ): X509KeyPair {
58
+ return measureBlock(function createX509() {
68
59
  let { domain, issuer, lifeSpan, keyPair } = config;
69
60
 
70
61
  let certObj = forge.pki.createCertificate();
71
- if ("publicKeyBytes" in keyPair) {
72
- certObj.publicKey = { publicKeyBytes: keyPair.publicKeyBytes, keyType: forge.pki.oids["EdDSA25519"] } as unknown as forge.pki.PublicKey;
73
- } else {
74
- certObj.publicKey = keyPair.publicKey;
75
- }
62
+ certObj.publicKey = keyPair.publicKey;
76
63
  certObj.serialNumber = "01";
77
64
  // Give it 5 minutes before now. If we give it too much time, it can look like the cert is really
78
65
  // old, which will trigger various processes to try to get a fresher one (as if it lasts for
@@ -127,18 +114,7 @@ export function createX509(
127
114
  certObj.setExtensions(extensions);
128
115
 
129
116
 
130
- let signResult = measureBlock(function sign(): MaybePromise<void> {
131
- let signingCryptoKey: CryptoKey | undefined;
132
- if (issuer === "self") {
133
- if ("publicKeyBytes" in keyPair) {
134
- signingCryptoKey = keyPair.privateKey;
135
- }
136
- } else if (issuer.key instanceof CryptoKey) {
137
- signingCryptoKey = issuer.key;
138
- }
139
- if (signingCryptoKey) {
140
- return signCertWithCryptoKey(certObj, signingCryptoKey);
141
- }
117
+ measureBlock(function sign() {
142
118
  if (issuer === "self") {
143
119
  certObj.sign(keyPair.privateKey as any, forge.md.sha256.create());
144
120
  } else {
@@ -146,44 +122,15 @@ export function createX509(
146
122
  }
147
123
  });
148
124
 
149
- function toPems() {
150
- return measureBlock(function toPems() {
151
- let key: Buffer | CryptoKey;
152
- if ("publicKeyBytes" in keyPair) {
153
- key = keyPair.privateKey;
154
- } else {
155
- key = Buffer.from(privateKeyToPem(keyPair.privateKey));
156
- }
157
- return {
158
- domain,
159
- cert: Buffer.from(forge.pki.certificateToPem(certObj)),
160
- key,
161
- };
162
- });
163
- }
164
- if (signResult instanceof Promise) {
165
- return signResult.then(toPems);
166
- }
167
- return toPems();
125
+ return measureBlock(function toPems() {
126
+ return {
127
+ domain,
128
+ cert: Buffer.from(forge.pki.certificateToPem(certObj)),
129
+ key: Buffer.from(privateKeyToPem(keyPair.privateKey)),
130
+ };
131
+ });
168
132
  });
169
133
  }
170
-
171
- // Replicates the forked forge's ed25519 cert.sign, except the signature is produced by
172
- // crypto.subtle, so the private key can be a non-extractable CryptoKey.
173
- async function signCertWithCryptoKey(certObj: forge.pki.Certificate, privateKey: CryptoKey): Promise<void> {
174
- certObj.signatureOid = certObj.siginfo.algorithmOid = forge.pki.oids["EdDSA25519"];
175
- let tbs = forge.pki.getTBSCertificate(certObj);
176
- let tbsDer = forge.asn1.toDer(tbs).getBytes();
177
- let signature = await globalThis.crypto.subtle.sign("Ed25519", privateKey, Buffer.from(tbsDer, "binary"));
178
- certObj.tbsCertificate = tbs;
179
- certObj.signature = Buffer.from(signature).toString("binary");
180
- }
181
-
182
- export async function generateNonExtractableKeyPair(): Promise<NonExtractableKeyPair> {
183
- let keyPair = await globalThis.crypto.subtle.generateKey("Ed25519", false, ["sign", "verify"]) as CryptoKeyPair;
184
- let publicKeyBytes = Buffer.from(await globalThis.crypto.subtle.exportKey("raw", keyPair.publicKey));
185
- return { publicKeyBytes, privateKey: keyPair.privateKey };
186
- }
187
134
  export function privateKeyToPem(buffer: forge.pki.PrivateKey | forge.Ed25519PrivateKey) {
188
135
  if ("privateKeyBytes" in buffer) {
189
136
  return forge.ed25519.privateKeyToPem(buffer);
@@ -225,13 +172,8 @@ function isED25519(key: string | Buffer) {
225
172
  }
226
173
 
227
174
  // EQUIVALENT TO: `crypto.createSign("SHA256").update(JSON.stringify(payload)).sign(keyCert.key, "binary")`
228
- export const sign = measureWrap(function sign(keyPair: { key: string | Buffer | CryptoKey }, data: unknown): MaybePromise<string> {
175
+ export const sign = measureWrap(function sign(keyPair: { key: string | Buffer }, data: unknown): string {
229
176
  let dataStr = JSON.stringify(data);
230
- if (keyPair.key instanceof CryptoKey) {
231
- // "binary" encoding matches how forge interprets the string on the verify side
232
- return globalThis.crypto.subtle.sign("Ed25519", keyPair.key, Buffer.from(dataStr, "binary"))
233
- .then(signature => Buffer.from(signature).toString("binary"));
234
- }
235
177
  if (isED25519(keyPair.key)) {
236
178
  let privateKey = (forge.pki.ed25519 as any).privateKeyFromPem(keyPair.key.toString());
237
179
  return privateKey.sign(dataStr);
@@ -245,8 +187,20 @@ export const sign = measureWrap(function sign(keyPair: { key: string | Buffer |
245
187
 
246
188
  export function verify(cert: string, signature: string, data: unknown) {
247
189
  let certObj = parseCert(cert);
248
- if (!(certObj.publicKey as forge.pki.rsa.PublicKey).verify(JSON.stringify(data), signature)) {
249
- throw new Error(`Signature verification failed. Signature: ${JSON.stringify(signature)} | Data: ${ellipsize(JSON.stringify(data), 1024)}`);
190
+ let publicKey = certObj.publicKey;
191
+ let dataStr = JSON.stringify(data);
192
+ let verified: boolean;
193
+ if ("publicKeyBytes" in publicKey) {
194
+ // ed25519 verifies the raw message (it hashes internally)
195
+ verified = (publicKey as unknown as { verify(message: string, signature: string): boolean }).verify(dataStr, signature);
196
+ } else {
197
+ // RSA verifies against the digest, matching the digest sign() creates
198
+ const md = forge.md.sha256.create();
199
+ md.update(dataStr);
200
+ verified = (publicKey as forge.pki.rsa.PublicKey).verify(md.digest().bytes(), signature);
201
+ }
202
+ if (!verified) {
203
+ throw new Error(`Signature verification failed. Signature: ${JSON.stringify(signature)} | Data: ${ellipsize(dataStr, 1024)}`);
250
204
  }
251
205
  }
252
206
 
@@ -431,6 +385,14 @@ function validateAltNames(certParsed: forge.pki.Certificate, subject: string) {
431
385
  }
432
386
 
433
387
 
388
+ // NOTE: We can't use crypto.subtle (non-extractable CryptoKeys) for our keys, because subtle
389
+ // is asynchronous, and everything here (key generation, cert creation, signing) must be
390
+ // available synchronously. The only real benefit of subtle is that a cross-site scripting
391
+ // attack can't exfiltrate the key. Which, while nice, is of minor benefit, as the cross-site
392
+ // script can already do quite a bit of damage anyway with access. And if that happens, the
393
+ // first thing the user is probably going to do is reset all their credentials, which solves
394
+ // the case of the key being exfiltrated anyway (by telling the server to stop trusting
395
+ // all identities).
434
396
  export function generateKeyPair() {
435
397
  return measureBlock(function generateKeyPair() {
436
398
  // NOTE: We use ED25519 because it can generated keys about 10X faster, WHICH, is still slow
@@ -460,68 +422,32 @@ export function generateTestCA(domain: string) {
460
422
  return createX509({ domain: fullDomain, issuer: "self", keyPair, lifeSpan: timeInDay * 365 * 20 });
461
423
  }
462
424
 
463
- let identityCA = cache((domain: string) => {
464
- let identityCA = lazy((async (): Promise<X509KeyPair> => {
465
- if (!isNode()) {
466
- let store = getBrowserIdentityStore(domain);
467
- let caCached = await store.get();
468
- if (!caCached) {
469
- console.log(`Generating new identity CA (non-extractable browser key)`);
470
- const keyPair = await generateNonExtractableKeyPair();
471
- let caPublicKeyPart = getDomainPartFromPublicKey(keyPair.publicKeyBytes);
472
- let fullDomain = `${caPublicKeyPart}.${domain}`;
473
- let value = await createX509({ domain: fullDomain, issuer: "self", keyPair, lifeSpan: timeInDay * 365 * 20 });
474
- caCached = {
475
- domain: value.domain,
476
- certB64: value.cert.toString("base64"),
477
- privateKey: keyPair.privateKey,
478
- };
479
- await store.set(caCached);
480
- }
481
- let result = {
482
- domain: caCached.domain,
483
- cert: Buffer.from(caCached.certB64, "base64"),
484
- key: caCached.privateKey,
485
- };
486
- trustCertificate(result.cert.toString());
487
- identityCA.set(result);
488
- return result;
489
- }
490
- let identityCACached = getIdentityStore(domain);
491
- let caCached = await identityCACached.get();
492
- if (!caCached) {
493
- console.log(`Generating new identity CA`);
494
- const keyPair = generateKeyPair();
495
- let caPublicKeyPart = getDomainPartFromPublicKey(keyPair.publicKey);
496
- let fullDomain = `${caPublicKeyPart}.${domain}`;
497
-
498
- let value = await createX509({ domain: fullDomain, issuer: "self", keyPair, lifeSpan: timeInDay * 365 * 20 });
499
-
500
- caCached = {
501
- domain: value.domain,
502
- certB64: value.cert.toString("base64"),
503
- keyB64: keyPairKeyToBuffer(value.key).toString("base64"),
504
- };
505
- await identityCACached.set(caCached);
506
- }
507
- let result = {
508
- domain: caCached.domain,
509
- cert: Buffer.from(caCached.certB64, "base64"),
510
- key: Buffer.from(caCached.keyB64, "base64"),
511
- };
512
- trustCertificate(result.cert.toString());
513
- identityCA.set(result);
514
- return result;
515
- }) as (() => MaybePromise<X509KeyPair>));
516
- return identityCA;
517
- });
425
+ let identityCA = cache((domain: string) => lazy((): X509KeyPair => {
426
+ let identityCACached = getIdentityStore(domain);
427
+ let caCached = identityCACached.get();
428
+ if (!caCached) {
429
+ console.log(`Generating new identity CA`);
430
+ const keyPair = generateKeyPair();
431
+ let caPublicKeyPart = getDomainPartFromPublicKey(keyPair.publicKey);
432
+ let fullDomain = `${caPublicKeyPart}.${domain}`;
433
+
434
+ let value = createX509({ domain: fullDomain, issuer: "self", keyPair, lifeSpan: timeInDay * 365 * 20 });
518
435
 
519
- function keyPairKeyToBuffer(key: Buffer | CryptoKey): Buffer {
520
- if (key instanceof CryptoKey) {
521
- throw new Error(`Cannot serialize a non-extractable CryptoKey`);
436
+ caCached = {
437
+ domain: value.domain,
438
+ certB64: value.cert.toString("base64"),
439
+ keyB64: value.key.toString("base64"),
440
+ };
441
+ identityCACached.set(caCached);
522
442
  }
523
- return key;
524
- }
443
+ let result = {
444
+ domain: caCached.domain,
445
+ cert: Buffer.from(caCached.certB64, "base64"),
446
+ key: Buffer.from(caCached.keyB64, "base64"),
447
+ };
448
+ trustCertificate(result.cert.toString());
449
+ return result;
450
+ }));
525
451
 
526
452
  // IMPORTANT! We do not embed any debug info in this domain. If we did, it would be useful,
527
453
  // but... potentally a security vulnerability, as if the debug info (such as a prefix)
@@ -531,22 +457,9 @@ function keyPairKeyToBuffer(key: Buffer | CryptoKey): Buffer {
531
457
  // (and hopefully stored in a UI, showing IP, time, etc).
532
458
  export function createCertFromCA(config: {
533
459
  CAKeyPair: X509KeyPair;
534
- }): MaybePromise<X509KeyPair> {
535
- return measureBlock(function createCertFromCA(): MaybePromise<X509KeyPair> {
460
+ }): X509KeyPair {
461
+ return measureBlock(function createCertFromCA() {
536
462
  let { CAKeyPair } = config;
537
- if (!isNode()) {
538
- return (async () => {
539
- const keyPair = await generateNonExtractableKeyPair();
540
- let domainKeyPart = getDomainPartFromPublicKey(keyPair.publicKeyBytes);
541
- let fullDomain = `${domainKeyPart}.${CAKeyPair.domain}`;
542
- return await createX509({
543
- domain: fullDomain,
544
- issuer: CAKeyPair,
545
- keyPair,
546
- lifeSpan: timeInDay * 365 * 10,
547
- });
548
- })();
549
- }
550
463
  const keyPair = generateKeyPair();
551
464
  let domainKeyPart = getDomainPartFromPublicKey(keyPair.publicKey);
552
465
  let fullDomain = `${domainKeyPart}.${config.CAKeyPair.domain}`;
@@ -611,27 +524,12 @@ export function encodeNodeId(parts: NodeIdParts) {
611
524
  }
612
525
 
613
526
  export async function setIdentityCARaw(domain: string, json: string) {
527
+ let identityCACached = getIdentityStore(domain);
614
528
  let obj = JSON.parse(json) as {
615
529
  domain: string;
616
530
  certB64: string;
617
531
  keyB64: string;
618
532
  };
619
- if (!isNode()) {
620
- // Import into a non-extractable CryptoKey, so once stored the key can't be exfiltrated
621
- let privateKey = await importEd25519PrivateKey(Buffer.from(obj.keyB64, "base64").toString());
622
- let ca = {
623
- domain: obj.domain,
624
- cert: Buffer.from(obj.certB64, "base64"),
625
- key: privateKey,
626
- };
627
- trustCertificate(ca.cert.toString());
628
- identityCA(domain).set(ca);
629
- getThreadKeyCertBase(domain).reset();
630
- await getBrowserIdentityStore(domain).set({ domain: obj.domain, certB64: obj.certB64, privateKey });
631
- resetAllNodeCallFactories();
632
- return;
633
- }
634
- let identityCACached = getIdentityStore(domain);
635
533
  let ca = {
636
534
  domain: obj.domain,
637
535
  cert: Buffer.from(obj.certB64, "base64"),
@@ -640,39 +538,23 @@ export async function setIdentityCARaw(domain: string, json: string) {
640
538
  trustCertificate(ca.cert.toString());
641
539
  identityCA(domain).set(ca);
642
540
  getThreadKeyCertBase(domain).reset();
643
- await identityCACached.set(obj);
541
+ identityCACached.set(obj);
644
542
  resetAllNodeCallFactories();
645
543
  }
646
544
 
647
- async function importEd25519PrivateKey(pem: string): Promise<CryptoKey> {
648
- let parsed = forge.ed25519.privateKeyFromPem(pem);
649
- let bytes = Buffer.from(parsed.privateKeyBytes || parsed);
650
- let seed = bytes;
651
- if (seed.length === 64) {
652
- // NaCl layout is seed || publicKey
653
- seed = seed.subarray(0, 32);
654
- }
655
- if (seed.length !== 32) {
656
- throw new Error(`Expected a 32 or 64 byte ed25519 private key, was ${bytes.length} bytes`);
657
- }
658
- let pkcs8 = Buffer.concat([Buffer.from("302e020100300506032b657004220420", "hex"), seed]);
659
- return await globalThis.crypto.subtle.importKey("pkcs8", pkcs8, "Ed25519", false, ["sign"]);
660
- }
661
-
545
+ // NOTE: The identity CA is available synchronously (storage is fs/localStorage, both
546
+ // synchronous), so this only exists for backwards compatibility with startup code
547
+ // that awaits it.
662
548
  export async function loadIdentityCA(domain: string) {
663
- await identityCA(domain)();
549
+ identityCA(domain)();
664
550
  }
665
551
  export function getIdentityCA(domain: string): X509KeyPair {
666
- let value = identityCA(domain)();
667
- if (value instanceof Promise) {
668
- throw new Error(`Identity CA is not yet loaded. Call and wait for loadIdentityCA(${JSON.stringify(domain)}) in your startup before accessing the identity (or call getIdentityCAPromise(${JSON.stringify(domain)}))`);
669
- }
670
- return value;
552
+ return identityCA(domain)();
671
553
  }
672
554
 
673
555
  // TODO: Replace this with a database, so it is easy for us to trust CAs
674
556
  // cross machine, and even have multiple users, etc, etc.
675
- export function getIdentityCAPromise(domain: string): MaybePromise<X509KeyPair> {
557
+ export function getIdentityCAPromise(domain: string): X509KeyPair {
676
558
  return identityCA(domain)();
677
559
  }
678
560
 
@@ -681,11 +563,7 @@ export function getOwnMachineId(domain: string) {
681
563
  return getMachineId(getIdentityCA(domain).domain, domain);
682
564
  }
683
565
  export function getOwnThreadId(domain: string) {
684
- let threadKeyCert = getThreadKeyCert(domain);
685
- if (threadKeyCert instanceof Promise) {
686
- throw new Error(`Thread key cert is not yet loaded. Await getThreadKeyCert(${JSON.stringify(domain)}) in your startup before accessing the threadId`);
687
- }
688
- return decodeNodeIdAssert(threadKeyCert.domain, domain).threadId;
566
+ return decodeNodeIdAssert(getThreadKeyCert(domain).domain, domain).threadId;
689
567
  }
690
568
 
691
569
  /** Part of the machineId comes from the publicKey, so we can use it to verify */
@@ -14,7 +14,4 @@ declare module "node-forge" {
14
14
  static publicKeyToPem(key: Ed25519PublicKey): string;
15
15
  static publicKeyFromPem(pem: string): Ed25519PublicKey;
16
16
  }
17
- namespace pki {
18
- function getTBSCertificate(cert: pki.Certificate): asn1.Asn1;
19
- }
20
17
  }
@@ -1,9 +1,4 @@
1
- import { MaybePromise } from "socket-function/src/types";
2
- export declare function getIDBKeyStore<T>(appName: string, key: string): {
3
- get(): Promise<T | undefined>;
4
- set(value: T | undefined): Promise<void>;
5
- };
6
1
  export declare function getKeyStore<T>(appName: string, key: string): {
7
- get(): MaybePromise<T | undefined>;
8
- set(value: T | null): MaybePromise<void>;
2
+ get(): T | undefined;
3
+ set(value: T | null): void;
9
4
  };
@@ -1,48 +1,11 @@
1
1
  import { isNode } from "socket-function/src/misc";
2
2
  import fs from "fs";
3
3
  import os from "os";
4
- import { MaybePromise } from "socket-function/src/types";
5
4
  import { cache } from "socket-function/src/caching";
6
5
 
7
- // Stores structured-cloneable values (including non-extractable CryptoKeys) in IndexedDB.
8
- export function getIDBKeyStore<T>(appName: string, key: string): {
9
- get(): Promise<T | undefined>;
10
- set(value: T | undefined): Promise<void>;
11
- } {
12
- async function withStore<R>(mode: IDBTransactionMode, fnc: (store: IDBObjectStore) => IDBRequest<R>): Promise<R> {
13
- let openReq = indexedDB.open(`keystore_${appName}`, 1);
14
- openReq.onupgradeneeded = () => openReq.result.createObjectStore("kv");
15
- let db = await new Promise<IDBDatabase>((resolve, reject) => {
16
- openReq.onsuccess = () => resolve(openReq.result);
17
- openReq.onerror = () => reject(openReq.error);
18
- });
19
- try {
20
- let req = fnc(db.transaction("kv", mode).objectStore("kv"));
21
- return await new Promise<R>((resolve, reject) => {
22
- req.onsuccess = () => resolve(req.result);
23
- req.onerror = () => reject(req.error);
24
- });
25
- } finally {
26
- db.close();
27
- }
28
- }
29
- return {
30
- async get() {
31
- return await withStore("readonly", store => store.get(key)) as T | undefined;
32
- },
33
- async set(value: T | undefined) {
34
- if (!value) {
35
- await withStore("readwrite", store => store.delete(key));
36
- return;
37
- }
38
- await withStore("readwrite", store => store.put(value, key));
39
- },
40
- };
41
- }
42
-
43
6
  export function getKeyStore<T>(appName: string, key: string): {
44
- get(): MaybePromise<T | undefined>;
45
- set(value: T | null): MaybePromise<void>;
7
+ get(): T | undefined;
8
+ set(value: T | null): void;
46
9
  } {
47
10
  if (isNode()) {
48
11
  let path = os.homedir() + `/keystore_${appName}_` + key + ".json";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sliftutils",
3
- "version": "1.5.2",
3
+ "version": "1.5.4",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "files": [