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
|
|
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
|
|
104
|
-
}):
|
|
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
|
|
111
|
-
}, data: unknown) =>
|
|
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):
|
|
106
|
+
export declare function generateTestCA(domain: string): X509KeyPair;
|
|
118
107
|
export declare function createCertFromCA(config: {
|
|
119
108
|
CAKeyPair: X509KeyPair;
|
|
120
|
-
}):
|
|
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):
|
|
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):
|
|
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():
|
|
240
|
-
set(value: T | null):
|
|
220
|
+
get(): T | undefined;
|
|
221
|
+
set(value: T | null): void;
|
|
241
222
|
};
|
|
242
223
|
|
|
243
224
|
}
|
package/misc/https/certs.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
37
|
-
}):
|
|
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
|
|
44
|
-
}, data: unknown) =>
|
|
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):
|
|
39
|
+
export declare function generateTestCA(domain: string): X509KeyPair;
|
|
51
40
|
export declare function createCertFromCA(config: {
|
|
52
41
|
CAKeyPair: X509KeyPair;
|
|
53
|
-
}):
|
|
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):
|
|
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):
|
|
64
|
+
export declare function getThreadKeyCert(domain: string): X509KeyPair;
|
|
76
65
|
export declare const createTestBrowserKeyCert: {
|
|
77
66
|
(): Promise<X509KeyPair>;
|
|
78
67
|
reset(): void;
|
package/misc/https/certs.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
55
|
+
} | forge.pki.KeyPair;
|
|
65
56
|
}
|
|
66
|
-
):
|
|
67
|
-
return measureBlock(function createX509()
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
|
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
|
-
|
|
249
|
-
|
|
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
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
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
|
-
|
|
520
|
-
|
|
521
|
-
|
|
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
|
-
|
|
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
|
-
}):
|
|
535
|
-
return measureBlock(function createCertFromCA()
|
|
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
|
-
|
|
541
|
+
identityCACached.set(obj);
|
|
644
542
|
resetAllNodeCallFactories();
|
|
645
543
|
}
|
|
646
544
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
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
|
-
|
|
549
|
+
identityCA(domain)();
|
|
664
550
|
}
|
|
665
551
|
export function getIdentityCA(domain: string): X509KeyPair {
|
|
666
|
-
|
|
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):
|
|
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
|
-
|
|
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 */
|
|
@@ -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():
|
|
8
|
-
set(value: T | null):
|
|
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():
|
|
45
|
-
set(value: T | null):
|
|
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";
|