sliftutils 1.0.4 → 1.1.1

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.
@@ -0,0 +1,568 @@
1
+ /// <reference path="./node-forge-ed25519.d.ts" />
2
+
3
+ module.allowclient = true;
4
+
5
+ // https://www.rfc-editor.org/rfc/rfc5280#page-42
6
+
7
+ import { setFlag } from "socket-function/require/compileFlags";
8
+ import * as forge from "node-forge";
9
+ import os from "os";
10
+ import fsSync from "fs";
11
+ import { cache, lazy } from "socket-function/src/caching";
12
+ import { isNode } from "socket-function/src/misc";
13
+ import sha265 from "js-sha256";
14
+ import crypto from "crypto";
15
+ import { trustCertificate } from "socket-function/src/certStore";
16
+ import { measureBlock, measureFnc, measureWrap } from "socket-function/src/profiling/measure";
17
+ import { getNodeIdDomain, getNodeIdDomainMaybeUndefined, getNodeIdLocation } from "socket-function/src/nodeCache";
18
+ import { MaybePromise } from "socket-function/src/types";
19
+ import { SocketFunction } from "socket-function/SocketFunction";
20
+ import { resetAllNodeCallFactories } from "socket-function/src/nodeCache";
21
+ import { getKeyStore } from "./persistentLocalStorage";
22
+
23
+ setFlag(require, "node-forge", "allowclient", true);
24
+ setFlag(require, "js-sha256", "allowclient", true);
25
+
26
+ const timeInDay = 1000 * 60 * 60 * 24;
27
+
28
+ export const CA_NOT_FOUND_ERROR = "18aa7318-f88f-4d2d-b41f-3daf4a433827";
29
+
30
+ export const identityStorageKey = "machineCA_10";
31
+ export type IdentityStorageType = { domain: string; certB64: string; keyB64: string };
32
+
33
+ function getIdentityStore(domain: string) {
34
+ return getKeyStore<IdentityStorageType>(domain, identityStorageKey);
35
+ }
36
+
37
+ export interface X509KeyPair { domain: string; cert: Buffer; key: Buffer; }
38
+
39
+ export function getCommonName(cert: Buffer | string) {
40
+ let subject = new crypto.X509Certificate(cert).subject;
41
+ let subjectKVPs = new Map(subject.split(",").map(x => x.trim().split("=")).map(x => [x[0], x.slice(1).join("=")]));
42
+ let commonName = subjectKVPs.get("CN");
43
+ if (!commonName) throw new Error(`No common name in subject: ${subject}`);
44
+ return commonName;
45
+ }
46
+
47
+ export function createX509(
48
+ config: {
49
+ domain: string;
50
+ issuer: X509KeyPair | "self";
51
+ lifeSpan: number;
52
+ keyPair: {
53
+ publicKey: forge.Ed25519PublicKey;
54
+ privateKey: forge.Ed25519PrivateKey;
55
+ } | forge.pki.KeyPair;
56
+ }
57
+ ): X509KeyPair {
58
+ return measureBlock(function createX509() {
59
+ let { domain, issuer, lifeSpan, keyPair } = config;
60
+
61
+ let certObj = forge.pki.createCertificate();
62
+ certObj.publicKey = keyPair.publicKey;
63
+ certObj.serialNumber = "01";
64
+ // Give it 5 minutes before now. If we give it too much time, it can look like the cert is really
65
+ // old, which will trigger various processes to try to get a fresher one (as if it lasts for
66
+ // 1 hour, but we set notBefore to 1 month ago, it looks 1 month old, and so almost expired,
67
+ // when it isn't...)
68
+ certObj.validity.notBefore = new Date(Date.now() - 1000 * 60 * 5);
69
+ certObj.validity.notAfter = new Date(Date.now() + lifeSpan);
70
+
71
+ const commonNameAttrs = [{ name: "commonName", value: domain }];
72
+ certObj.setSubject(commonNameAttrs);
73
+
74
+ if (issuer === "self") {
75
+ certObj.setIssuer(commonNameAttrs);
76
+ } else {
77
+ certObj.setIssuer(forge.pki.certificateFromPem(issuer.cert.toString()).subject.attributes);
78
+ }
79
+
80
+ let extensions = [];
81
+ const isCA = issuer === "self";
82
+ if (isCA) {
83
+ extensions.push({ name: "basicConstraints", cA: true });
84
+ }
85
+
86
+ let localHostDomain = "127-0-0-1." + domain.split(".").slice(-2).join(".");
87
+
88
+ extensions.push(...[
89
+ { name: "keyUsage", keyCertSign: isCA, digitalSignature: true, nonRepudiation: true, keyEncipherment: true, dataEncipherment: true },
90
+ { name: "subjectKeyIdentifier" },
91
+ {
92
+ name: "subjectAltName",
93
+ altNames: [
94
+ { type: 2, value: domain },
95
+ { type: 2, value: "*." + domain },
96
+ { type: 2, value: localHostDomain },
97
+ // NOTE: No longer allow 127.0.0.1, to make this more secure. We might enable this
98
+ // behavior behind a flag, for development.
99
+ //{ type: 7, ip: "127.0.0.1" }
100
+ ]
101
+ },
102
+ // NOTE: nameConstraints are supported with our branch. But... chrome doesn't support them, so there's no point in using them.
103
+ // "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
+ // },
117
+ ]);
118
+ certObj.setExtensions(extensions);
119
+
120
+
121
+ measureBlock(function sign() {
122
+ if (issuer === "self") {
123
+ certObj.sign(keyPair.privateKey as any, forge.md.sha256.create());
124
+ } else {
125
+ certObj.sign(privateKeyFromPem(issuer.key.toString()) as any, forge.md.sha256.create());
126
+ }
127
+ });
128
+
129
+ return measureBlock(function toPems() {
130
+ return {
131
+ domain,
132
+ cert: Buffer.from(forge.pki.certificateToPem(certObj)),
133
+ key: Buffer.from(privateKeyToPem(keyPair.privateKey)),
134
+ };
135
+ });
136
+ });
137
+ }
138
+ export function privateKeyToPem(buffer: forge.pki.PrivateKey | forge.Ed25519PrivateKey) {
139
+ if ("privateKeyBytes" in buffer) {
140
+ return forge.ed25519.privateKeyToPem(buffer);
141
+ }
142
+ return forge.pki.privateKeyToPem(buffer);
143
+ }
144
+ function privateKeyFromPem(pem: string) {
145
+ // We want to guess the type correctly, as caught exceptions make debugging annoying
146
+ if (pem.length < 200) {
147
+ try {
148
+ return forge.ed25519.privateKeyFromPem(pem);
149
+ } catch { }
150
+ }
151
+ try {
152
+ return forge.pki.privateKeyFromPem(pem);
153
+ } catch {
154
+ return forge.ed25519.privateKeyFromPem(pem);
155
+ }
156
+ }
157
+ function publicKeyFromCert(cert: string) {
158
+ return parseCert(cert).publicKey;
159
+ }
160
+ export function parseCert(PEMorDER: string | Buffer) {
161
+ return forge.pki.certificateFromPem(normalizeCertToPEM(PEMorDER));
162
+ }
163
+
164
+ // Gets a unique value to represent the public key
165
+ export function getPublicIdentifier(PEMorDER: string | Buffer): Buffer {
166
+ let obj = parseCert(PEMorDER);
167
+ let publicKey = obj.publicKey;
168
+ if ("publicKeyBytes" in publicKey) {
169
+ return Buffer.from(publicKey.publicKeyBytes as any);
170
+ }
171
+ return Buffer.from(new Uint32Array((publicKey as any).n.data).buffer);
172
+ }
173
+
174
+ function isED25519(key: string | Buffer) {
175
+ return key.length < 256;
176
+ }
177
+
178
+ // EQUIVALENT TO: `crypto.createSign("SHA256").update(JSON.stringify(payload)).sign(keyCert.key, "binary")`
179
+ export const sign = measureWrap(function sign(keyPair: { key: string | Buffer }, data: unknown): string {
180
+ let dataStr = JSON.stringify(data);
181
+ if (isED25519(keyPair.key)) {
182
+ let privateKey = (forge.pki.ed25519 as any).privateKeyFromPem(keyPair.key.toString());
183
+ return privateKey.sign(dataStr);
184
+ } else {
185
+ let privateKey = forge.pki.privateKeyFromPem(keyPair.key.toString());
186
+ const md = forge.md.sha256.create();
187
+ md.update(dataStr);
188
+ return privateKey.sign(md);
189
+ }
190
+ });
191
+
192
+ export function verify(cert: string, signature: string, data: unknown) {
193
+ let certObj = parseCert(cert);
194
+ return (certObj.publicKey as forge.pki.rsa.PublicKey).verify(JSON.stringify(data), signature);
195
+ }
196
+
197
+ function normalizeCertToPEM(PEMorDER: string | Buffer): string {
198
+ if (PEMorDER.toString().startsWith("-----BEGIN CERTIFICATE-----")) {
199
+ return PEMorDER.toString();
200
+ }
201
+ PEMorDER = PEMorDER.toString("base64");
202
+ return "-----BEGIN CERTIFICATE-----\n" + PEMorDER + "\n-----END CERTIFICATE-----";
203
+ }
204
+
205
+ function getDomainPartFromPublicKey(publicKey: { publicKeyBytes: Buffer } | forge.pki.KeyPair["publicKey"] | Buffer) {
206
+ let bytes: Buffer;
207
+ if ("publicKeyBytes" in publicKey) {
208
+ bytes = publicKey.publicKeyBytes;
209
+ } else if (publicKey instanceof Buffer) {
210
+ bytes = publicKey;
211
+ } else {
212
+ bytes = Buffer.from(new Uint32Array((publicKey as any).n.data).buffer);
213
+ }
214
+ return "b" + sha265.sha256(Buffer.from(bytes)).slice(0, 16).replaceAll("+", "-").replaceAll("/", "_");
215
+ }
216
+
217
+ export function validateCACert(domain: string, cert: string | Buffer) {
218
+ let certParsed = parseCert(cert);
219
+
220
+ let subject = certParsed.subject.getField("CN").value as string;
221
+ let localhostDomain = "127-0-0-1." + subject.split(".").slice(-2).join(".");
222
+
223
+ let domainParts = subject.split(".").reverse();
224
+
225
+ let rootDomainParsed = [domainParts.shift(), domainParts.shift()].reverse().join(".");
226
+ if (rootDomainParsed !== domain) {
227
+ // This is important, as our trust store contains more then just OUR certificates,
228
+ // so if we allow any domains then real domains can impersonate anyone! It has to
229
+ // be one OUR domain to be trusted!
230
+ throw new Error(`Certificate root domain should be ${domain}, but is ${rootDomainParsed}`);
231
+ }
232
+ // TODO: Maybe just skip if it isn't a hash string?
233
+ if (domainParts[0] === "noproxy") {
234
+ domainParts.shift();
235
+ }
236
+
237
+ let certExpectedPublicKeyPart = (domainParts.shift() || "").split("-").slice(-1)[0];
238
+ let certActualPublicKeyPart = getDomainPartFromPublicKey(certParsed.publicKey);
239
+ if (certExpectedPublicKeyPart !== certActualPublicKeyPart) {
240
+ throw new Error(`Certificate public key in the url is ${certExpectedPublicKeyPart}, but in the cert is ${certActualPublicKeyPart}`);
241
+ }
242
+
243
+ // ALSO, require name constraints to be present, and to restrict to the "CN"
244
+ let nameConstraints = certParsed.getExtension("nameConstraints") as any;
245
+ if (!nameConstraints) {
246
+ throw new Error(`Certificate must have nameConstraints`);
247
+ }
248
+ let subtrees = nameConstraints.permittedSubtrees;
249
+ if (!subtrees) {
250
+ throw new Error(`Certificate must have nameConstraints.permittedSubtrees`);
251
+ }
252
+ let subtreeValues = subtrees.map((x: any) => x.value);
253
+ // Ignore localhostDomain, as it can always safely be allowed (the same machine
254
+ // is always allowed).
255
+ subtreeValues = subtreeValues.filter((x: string) => x !== localhostDomain);
256
+ if (subtreeValues.length !== 1 || subtreeValues[0] !== subject) {
257
+ throw new Error(`Certificate must have a single constrained domain (had ${JSON.stringify(subtreeValues)})`);
258
+ }
259
+
260
+ validateAltNames(certParsed, subject);
261
+ }
262
+
263
+ export function validateCertificate(domain: string, cert: Buffer | string, issuerCert: Buffer | string) {
264
+ validateCACert(domain, issuerCert);
265
+
266
+ let certParsed = parseCert(cert);
267
+ let subject = certParsed.subject.getField("CN").value as string;
268
+ let localhostDomain = "127-0-0-1." + subject.split(".").slice(-2).join(".");
269
+
270
+ let domainParts = subject.split(".").reverse();
271
+
272
+ let rootDomainParsed = [domainParts.shift(), domainParts.shift()].reverse().join(".");
273
+ if (rootDomainParsed !== domain) {
274
+ throw new Error(`Certificate root domain should be ${domain}, but is ${rootDomainParsed}`);
275
+ }
276
+ // TODO: Maybe just skip if it isn't a hash string?
277
+ if (domainParts[0] === "noproxy") {
278
+ domainParts.shift();
279
+ }
280
+
281
+ let issuerCertParsed = parseCert(issuerCert);
282
+
283
+ let issuerExpectedPublicKeyPart = domainParts.shift() || "";
284
+ let issuerActualPublicKeyPart = getDomainPartFromPublicKey(issuerCertParsed.publicKey);
285
+ if (issuerExpectedPublicKeyPart !== issuerActualPublicKeyPart) {
286
+ throw new Error(`Issuer public key in the url is ${issuerExpectedPublicKeyPart}, but in the cert is ${issuerActualPublicKeyPart}`);
287
+ }
288
+
289
+ // Take the last part
290
+ let certExpectedPublicKeyPart = domainParts.shift() || "";
291
+ let certActualPublicKeyPart = getDomainPartFromPublicKey(certParsed.publicKey);
292
+ if (certExpectedPublicKeyPart !== certActualPublicKeyPart) {
293
+ throw new Error(`Certificate public key in the url is ${certExpectedPublicKeyPart}, but in the cert is ${certActualPublicKeyPart}`);
294
+ }
295
+
296
+
297
+ let nameConstraints = issuerCertParsed.getExtension("nameConstraints") as any;
298
+ if (!nameConstraints) {
299
+ throw new Error(`CA must have nameConstraints`);
300
+ }
301
+ let subtrees = nameConstraints.permittedSubtrees;
302
+ if (!subtrees) {
303
+ throw new Error(`CA must have nameConstraints.permittedSubtrees`);
304
+ }
305
+ let subtreeValues = subtrees.map((x: any) => x.value);
306
+ // Ignore localhostDomain, as it can always safely be allowed (the same machine
307
+ // is always allowed).
308
+ subtreeValues = subtreeValues.filter((x: string) => x !== localhostDomain);
309
+ if (subtreeValues.length !== 1) {
310
+ throw new Error(`CA must have a single constrained domain (had ${JSON.stringify(subtreeValues)})`);
311
+ }
312
+
313
+ let subtree = subtreeValues[0];
314
+ if (subtree !== subject && !subject.endsWith("." + subtree)) {
315
+ throw new Error(`Certificate must be a subtree of the CA (CA: ${subtree}, cert: ${subject})`);
316
+ }
317
+
318
+ validateAltNames(certParsed, subject);
319
+
320
+ // Verify issuer ACTUALLY signed certParsed
321
+ if (!issuerCertParsed.verify(certParsed)) {
322
+ throw new Error(`Issuer did not sign certificate`);
323
+ }
324
+ }
325
+
326
+ // Require alt names to be either equal to "CN", or a subtree of "CN"
327
+ function validateAltNames(certParsed: forge.pki.Certificate, subject: string) {
328
+ let localhostDomain = "127-0-0-1." + subject.split(".").slice(-2).join(".");
329
+
330
+ let altNamesObj = certParsed.getExtension("subjectAltName") as any;
331
+ let altNames = altNamesObj?.altNames.map((x: any) => x.value);
332
+ // Allow localhostDomain, as it can always safely be allowed
333
+ altNames = altNames.filter((x: string) => x !== localhostDomain);
334
+ if (
335
+ altNames.some((x: string) =>
336
+ !(
337
+ x === subject
338
+ || x.endsWith("." + subject)
339
+ // Commented out, because... it is so easy to publish a 127.0.0.1 A record,
340
+ // and even to generate a real cert, so, we should jsut do that, and keep it secure.
341
+ // If we need this for development we can put it behind a flag, so non-development
342
+ // instances are still secure.
343
+ // // Also allow 127.0.0.1, for local testing, for now?
344
+ // // - This might be insecure if these are trusted by the browser,
345
+ // // as then anyone that stores any cookies in this ip can have
346
+ // // the cookies stolen. But... if this is just cross-server...
347
+ // // I don't see how this could cause a security vulnerability.
348
+ // || x === Buffer.from([127, 0, 0, 1]).toString()
349
+ )
350
+ )
351
+ ) {
352
+ throw new Error(`Invalid alt names. Must be subtrees of the subject (CN) ${JSON.stringify(subject)}, was ${JSON.stringify(altNames)}`);
353
+ }
354
+ }
355
+
356
+
357
+ export function generateKeyPair() {
358
+ return measureBlock(function generateKeyPair() {
359
+ // NOTE: We use ED25519 because it can generated keys about 10X faster, WHICH, is still slow
360
+ // (~6ms on my machine). So we DEFINITELY don't want it to be 10X slower!
361
+ // NOTE: ED25519 doens't have great support in browsers, but we shouldn't need self signed certificates
362
+ // in the browser anyway.
363
+ // - https://security.stackexchange.com/a/236943/282367
364
+ //let keyPair = forge.ed25519.generateKeyPair();
365
+ let keyPair = forge.pki.rsa.generateKeyPair();
366
+ return keyPair;
367
+ });
368
+ }
369
+ export function generateRSAKeyPair() {
370
+ return measureBlock(function generateKeyPair() {
371
+ return forge.pki.rsa.generateKeyPair();
372
+ });
373
+ }
374
+
375
+ export function generateTestCA(domain: string) {
376
+ const keyPair = generateKeyPair();
377
+ let caPublicKeyPart = getDomainPartFromPublicKey(keyPair.publicKey);
378
+ let fullDomain = `${caPublicKeyPart}.${domain}`;
379
+ if (!isNode()) {
380
+ fullDomain = `${caPublicKeyPart}.${domain}`;
381
+ }
382
+
383
+ return createX509({ domain: fullDomain, issuer: "self", keyPair, lifeSpan: timeInDay * 365 * 20 });
384
+ }
385
+
386
+ let identityCA = cache((domain: string) => {
387
+ let identityCA = lazy((async (): Promise<X509KeyPair> => {
388
+ let identityCACached = getIdentityStore(domain);
389
+ let caCached = await identityCACached.get();
390
+ if (!caCached) {
391
+ console.log(`Generating new identity CA`);
392
+ const keyPair = generateKeyPair();
393
+ let caPublicKeyPart = getDomainPartFromPublicKey(keyPair.publicKey);
394
+ let fullDomain = `${caPublicKeyPart}.${domain}`;
395
+ if (!isNode()) {
396
+ fullDomain = `${caPublicKeyPart}.${domain}`;
397
+ }
398
+
399
+ let value = createX509({ domain: fullDomain, issuer: "self", keyPair, lifeSpan: timeInDay * 365 * 20 });
400
+
401
+ caCached = {
402
+ domain: value.domain,
403
+ certB64: value.cert.toString("base64"),
404
+ keyB64: value.key.toString("base64"),
405
+ };
406
+ await identityCACached.set(caCached);
407
+ }
408
+ let result = {
409
+ domain: caCached.domain,
410
+ cert: Buffer.from(caCached.certB64, "base64"),
411
+ key: Buffer.from(caCached.keyB64, "base64"),
412
+ };
413
+ trustCertificate(result.cert.toString());
414
+ identityCA.set(result);
415
+ return result;
416
+ }) as (() => MaybePromise<X509KeyPair>));
417
+ return identityCA;
418
+ });
419
+
420
+ // IMPORTANT! We do not embed any debug info in this domain. If we did, it would be useful,
421
+ // but... potentally a security vulnerability, as if the debug info (such as a prefix)
422
+ // is used to identify what a certificate is for, it would be easy for an attack to
423
+ // forge this (as the debug info won't be secured). So it is much better to keep
424
+ // the certificate opaque, and then require any metadata to be actually vetted
425
+ // (and hopefully stored in a UI, showing IP, time, etc).
426
+ export function createCertFromCA(config: {
427
+ CAKeyPair: X509KeyPair;
428
+ }): X509KeyPair {
429
+ return measureBlock(function createCertFromCA() {
430
+ let { CAKeyPair } = config;
431
+ const keyPair = generateKeyPair();
432
+ let domainKeyPart = getDomainPartFromPublicKey(keyPair.publicKey);
433
+ let fullDomain = `${domainKeyPart}.${config.CAKeyPair.domain}`;
434
+ return createX509({
435
+ domain: fullDomain,
436
+ issuer: CAKeyPair,
437
+ keyPair,
438
+ lifeSpan: timeInDay * 365 * 10,
439
+ });
440
+ });
441
+ }
442
+
443
+ export function getMachineId(domainName: string) {
444
+ return domainName.split(".").slice(-3).join(".");
445
+ }
446
+
447
+ export type NodeIdParts = {
448
+ threadId: string;
449
+ machineId: string;
450
+ domain: string;
451
+ port: number;
452
+ };
453
+ export function decodeNodeId(nodeId: string): NodeIdParts | undefined {
454
+ let locationObj = getNodeIdLocation(nodeId);
455
+ if (!locationObj) {
456
+ return undefined;
457
+ }
458
+ let parts = locationObj.address.split(".");
459
+ if (nodeId.startsWith("127-0-0-1.") && parts.length === 3) {
460
+ return {
461
+ threadId: "",
462
+ machineId: parts.at(-3) || "",
463
+ domain: parts.slice(-2).join("."),
464
+ port: locationObj.port,
465
+ };
466
+ }
467
+ if (parts.length < 4) {
468
+ return undefined;
469
+ }
470
+ return {
471
+ threadId: parts.at(-4) || "",
472
+ machineId: parts.at(-3) || "",
473
+ domain: parts.slice(-2).join(".") || "",
474
+ port: locationObj.port,
475
+ };
476
+ }
477
+ export function decodeNodeIdAssert(nodeId: string): NodeIdParts {
478
+ let result = decodeNodeId(nodeId);
479
+ if (!result) {
480
+ throw new Error(`Invalid nodeId: ${nodeId}`);
481
+ }
482
+ return result;
483
+ }
484
+ export function encodeNodeId(parts: NodeIdParts) {
485
+ return `${parts.threadId}.${parts.machineId}.${parts.domain}:${parts.port}`;
486
+ }
487
+
488
+ export async function setIdentityCARaw(domain: string, json: string) {
489
+ let identityCACached = getIdentityStore(domain);
490
+ let obj = JSON.parse(json) as {
491
+ domain: string;
492
+ certB64: string;
493
+ keyB64: string;
494
+ };
495
+ let ca = {
496
+ domain: obj.domain,
497
+ cert: Buffer.from(obj.certB64, "base64"),
498
+ key: Buffer.from(obj.keyB64, "base64"),
499
+ };
500
+ trustCertificate(ca.cert.toString());
501
+ identityCA(domain).set(ca);
502
+ getThreadKeyCertBase(domain).reset();
503
+ await identityCACached.set(obj);
504
+ resetAllNodeCallFactories();
505
+ }
506
+
507
+ export async function loadIdentityCA(domain: string) {
508
+ await identityCA(domain)();
509
+ }
510
+ export function getIdentityCA(domain: string): X509KeyPair {
511
+ let value = identityCA(domain)();
512
+ if (value instanceof Promise) {
513
+ throw new Error("Identity CA is not yet loaded. Call and wait for loadIdentityCA() in your startup before accessing the identity (or call getIdentityCAPromise())");
514
+ }
515
+ return value;
516
+ }
517
+
518
+ // TODO: Replace this with a database, so it is easy for us to trust CAs
519
+ // cross machine, and even have multiple users, etc, etc.
520
+ export function getIdentityCAPromise(domain: string): MaybePromise<X509KeyPair> {
521
+ return identityCA(domain)();
522
+ }
523
+
524
+
525
+ export function getOwnMachineId(domain: string) {
526
+ return getMachineId(getIdentityCA(domain).domain);
527
+ }
528
+
529
+ /** Part of the machineId comes from the publicKey, so we can use it to verify */
530
+ export function verifyMachineIdForPublicKey(config: {
531
+ machineId: string;
532
+ publicKey: Buffer;
533
+ }): boolean {
534
+ let { machineId, publicKey } = config;
535
+ let domainPart = getDomainPartFromPublicKey(publicKey);
536
+ return machineId.split(".").at(-3) === domainPart;
537
+ }
538
+
539
+ // NOTE: We don't have a cache per CA, as... the CA should be set first
540
+ // TODO: Maybe throw if they try to change the CA after they generate any certificates?
541
+ // TODO: Regenerate certificates after enough time (as thread certs should be relatively short lived,
542
+ // so it is plausible for them to expire)
543
+ // - We will also need to provide a callback so that users of the cert can update the cert they
544
+ // are using as well.
545
+ export function getThreadKeyCert(domain: string) {
546
+ return getThreadKeyCertBase(domain)();
547
+ }
548
+ const getThreadKeyCertBase = cache((domain: string) => lazy(() => {
549
+ let ca = getIdentityCA(domain);
550
+ return createCertFromCA({ CAKeyPair: ca });
551
+ }));
552
+
553
+ export const createTestBrowserKeyCert = lazy(async () => {
554
+ let keyPair = generateRSAKeyPair();
555
+ return await createX509({ domain: "test", issuer: "self", keyPair, lifeSpan: timeInDay * 365 * 20 });
556
+ });
557
+
558
+ export function getOwnNodeId(): string {
559
+ let nodeId = SocketFunction.mountedNodeId;
560
+ if (!nodeId) {
561
+ throw new Error(`Node must be mounted before nodeId is accessed`);
562
+ }
563
+ return nodeId;
564
+ }
565
+
566
+ export function getOwnNodeIdAllowUndefined() {
567
+ return SocketFunction.mountedNodeId;
568
+ }