sliftutils 1.5.1 → 1.5.3
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,26 +72,17 @@ 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 = "
|
|
75
|
+
export declare const identityStorageKey = "machineCA_12";
|
|
76
76
|
export type IdentityStorageType = {
|
|
77
77
|
domain: string;
|
|
78
78
|
certB64: string;
|
|
79
79
|
keyB64: string;
|
|
80
80
|
};
|
|
81
|
-
export type BrowserIdentityStorageType = {
|
|
82
|
-
domain: string;
|
|
83
|
-
certB64: string;
|
|
84
|
-
privateKey: CryptoKey;
|
|
85
|
-
};
|
|
86
81
|
export interface X509KeyPair {
|
|
87
82
|
domain: string;
|
|
88
83
|
cert: Buffer;
|
|
89
|
-
key: Buffer
|
|
84
|
+
key: Buffer;
|
|
90
85
|
}
|
|
91
|
-
export type NonExtractableKeyPair = {
|
|
92
|
-
publicKeyBytes: Buffer;
|
|
93
|
-
privateKey: CryptoKey;
|
|
94
|
-
};
|
|
95
86
|
export declare function getCommonName(cert: Buffer | string): string;
|
|
96
87
|
export declare function createX509(config: {
|
|
97
88
|
domain: string;
|
|
@@ -100,24 +91,23 @@ declare module "sliftutils/misc/https/certs" {
|
|
|
100
91
|
keyPair: {
|
|
101
92
|
publicKey: forge.Ed25519PublicKey;
|
|
102
93
|
privateKey: forge.Ed25519PrivateKey;
|
|
103
|
-
} | forge.pki.KeyPair
|
|
104
|
-
}):
|
|
105
|
-
export declare function generateNonExtractableKeyPair(): Promise<NonExtractableKeyPair>;
|
|
94
|
+
} | forge.pki.KeyPair;
|
|
95
|
+
}): X509KeyPair;
|
|
106
96
|
export declare function privateKeyToPem(buffer: forge.pki.PrivateKey | forge.Ed25519PrivateKey): string;
|
|
107
97
|
export declare function parseCert(PEMorDER: string | Buffer): forge.pki.Certificate;
|
|
108
98
|
export declare function getPublicIdentifier(PEMorDER: string | Buffer): Buffer;
|
|
109
99
|
export declare const sign: (keyPair: {
|
|
110
|
-
key: string | Buffer
|
|
111
|
-
}, data: unknown) =>
|
|
100
|
+
key: string | Buffer;
|
|
101
|
+
}, data: unknown) => string;
|
|
112
102
|
export declare function verify(cert: string, signature: string, data: unknown): void;
|
|
113
103
|
export declare function validateCACert(domain: string, cert: string | Buffer): void;
|
|
114
104
|
export declare function validateCertificate(domain: string, cert: Buffer | string, issuerCert: Buffer | string): void;
|
|
115
105
|
export declare function generateKeyPair(): forge.pki.rsa.KeyPair;
|
|
116
106
|
export declare function generateRSAKeyPair(): forge.pki.rsa.KeyPair;
|
|
117
|
-
export declare function generateTestCA(domain: string):
|
|
107
|
+
export declare function generateTestCA(domain: string): X509KeyPair;
|
|
118
108
|
export declare function createCertFromCA(config: {
|
|
119
109
|
CAKeyPair: X509KeyPair;
|
|
120
|
-
}):
|
|
110
|
+
}): X509KeyPair;
|
|
121
111
|
export declare function getMachineId(domainNameOrNodeId: string, domain: string): string;
|
|
122
112
|
export type NodeIdParts = {
|
|
123
113
|
threadId: string;
|
|
@@ -139,7 +129,7 @@ declare module "sliftutils/misc/https/certs" {
|
|
|
139
129
|
machineId: string;
|
|
140
130
|
publicKey: Buffer;
|
|
141
131
|
}): boolean;
|
|
142
|
-
export declare function getThreadKeyCert(domain: string):
|
|
132
|
+
export declare function getThreadKeyCert(domain: string): X509KeyPair;
|
|
143
133
|
export declare const createTestBrowserKeyCert: {
|
|
144
134
|
(): Promise<X509KeyPair>;
|
|
145
135
|
reset(): void;
|
|
@@ -223,18 +213,11 @@ declare module "sliftutils/misc/https/node-forge-ed25519" {
|
|
|
223
213
|
static publicKeyToPem(key: Ed25519PublicKey): string;
|
|
224
214
|
static publicKeyFromPem(pem: string): Ed25519PublicKey;
|
|
225
215
|
}
|
|
226
|
-
namespace pki {
|
|
227
|
-
function getTBSCertificate(cert: pki.Certificate): asn1.Asn1;
|
|
228
|
-
}
|
|
229
216
|
}
|
|
230
217
|
}
|
|
231
218
|
|
|
232
219
|
declare module "sliftutils/misc/https/persistentLocalStorage" {
|
|
233
220
|
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
221
|
export declare function getKeyStore<T>(appName: string, key: string): {
|
|
239
222
|
get(): MaybePromise<T | undefined>;
|
|
240
223
|
set(value: T | null): MaybePromise<void>;
|
package/misc/https/certs.d.ts
CHANGED
|
@@ -5,26 +5,17 @@
|
|
|
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 = "
|
|
8
|
+
export declare const identityStorageKey = "machineCA_12";
|
|
9
9
|
export type IdentityStorageType = {
|
|
10
10
|
domain: string;
|
|
11
11
|
certB64: string;
|
|
12
12
|
keyB64: string;
|
|
13
13
|
};
|
|
14
|
-
export type BrowserIdentityStorageType = {
|
|
15
|
-
domain: string;
|
|
16
|
-
certB64: string;
|
|
17
|
-
privateKey: CryptoKey;
|
|
18
|
-
};
|
|
19
14
|
export interface X509KeyPair {
|
|
20
15
|
domain: string;
|
|
21
16
|
cert: Buffer;
|
|
22
|
-
key: Buffer
|
|
17
|
+
key: Buffer;
|
|
23
18
|
}
|
|
24
|
-
export type NonExtractableKeyPair = {
|
|
25
|
-
publicKeyBytes: Buffer;
|
|
26
|
-
privateKey: CryptoKey;
|
|
27
|
-
};
|
|
28
19
|
export declare function getCommonName(cert: Buffer | string): string;
|
|
29
20
|
export declare function createX509(config: {
|
|
30
21
|
domain: string;
|
|
@@ -33,24 +24,23 @@ export declare function createX509(config: {
|
|
|
33
24
|
keyPair: {
|
|
34
25
|
publicKey: forge.Ed25519PublicKey;
|
|
35
26
|
privateKey: forge.Ed25519PrivateKey;
|
|
36
|
-
} | forge.pki.KeyPair
|
|
37
|
-
}):
|
|
38
|
-
export declare function generateNonExtractableKeyPair(): Promise<NonExtractableKeyPair>;
|
|
27
|
+
} | forge.pki.KeyPair;
|
|
28
|
+
}): X509KeyPair;
|
|
39
29
|
export declare function privateKeyToPem(buffer: forge.pki.PrivateKey | forge.Ed25519PrivateKey): string;
|
|
40
30
|
export declare function parseCert(PEMorDER: string | Buffer): forge.pki.Certificate;
|
|
41
31
|
export declare function getPublicIdentifier(PEMorDER: string | Buffer): Buffer;
|
|
42
32
|
export declare const sign: (keyPair: {
|
|
43
|
-
key: string | Buffer
|
|
44
|
-
}, data: unknown) =>
|
|
33
|
+
key: string | Buffer;
|
|
34
|
+
}, data: unknown) => string;
|
|
45
35
|
export declare function verify(cert: string, signature: string, data: unknown): void;
|
|
46
36
|
export declare function validateCACert(domain: string, cert: string | Buffer): void;
|
|
47
37
|
export declare function validateCertificate(domain: string, cert: Buffer | string, issuerCert: Buffer | string): void;
|
|
48
38
|
export declare function generateKeyPair(): forge.pki.rsa.KeyPair;
|
|
49
39
|
export declare function generateRSAKeyPair(): forge.pki.rsa.KeyPair;
|
|
50
|
-
export declare function generateTestCA(domain: string):
|
|
40
|
+
export declare function generateTestCA(domain: string): X509KeyPair;
|
|
51
41
|
export declare function createCertFromCA(config: {
|
|
52
42
|
CAKeyPair: X509KeyPair;
|
|
53
|
-
}):
|
|
43
|
+
}): X509KeyPair;
|
|
54
44
|
export declare function getMachineId(domainNameOrNodeId: string, domain: string): string;
|
|
55
45
|
export type NodeIdParts = {
|
|
56
46
|
threadId: string;
|
|
@@ -72,7 +62,7 @@ export declare function verifyMachineIdForPublicKey(config: {
|
|
|
72
62
|
machineId: string;
|
|
73
63
|
publicKey: Buffer;
|
|
74
64
|
}): boolean;
|
|
75
|
-
export declare function getThreadKeyCert(domain: string):
|
|
65
|
+
export declare function getThreadKeyCert(domain: string): X509KeyPair;
|
|
76
66
|
export declare const createTestBrowserKeyCert: {
|
|
77
67
|
(): Promise<X509KeyPair>;
|
|
78
68
|
reset(): void;
|
package/misc/https/certs.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { getNodeIdDomain, getNodeIdDomainMaybeUndefined, getNodeIdLocation } fro
|
|
|
18
18
|
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
|
-
import { getKeyStore
|
|
21
|
+
import { getKeyStore } from "./persistentLocalStorage";
|
|
22
22
|
import { ellipsize } from "../strings";
|
|
23
23
|
|
|
24
24
|
setFlag(require, "node-forge", "allowclient", true);
|
|
@@ -28,22 +28,14 @@ const timeInDay = 1000 * 60 * 60 * 24;
|
|
|
28
28
|
|
|
29
29
|
export const CA_NOT_FOUND_ERROR = "18aa7318-f88f-4d2d-b41f-3daf4a433827";
|
|
30
30
|
|
|
31
|
-
export const identityStorageKey = "
|
|
31
|
+
export const identityStorageKey = "machineCA_12";
|
|
32
32
|
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
33
|
|
|
38
34
|
function getIdentityStore(domain: string) {
|
|
39
35
|
return getKeyStore<IdentityStorageType>(domain, identityStorageKey);
|
|
40
36
|
}
|
|
41
|
-
function getBrowserIdentityStore(domain: string) {
|
|
42
|
-
return getIDBKeyStore<BrowserIdentityStorageType>(domain, identityStorageKey);
|
|
43
|
-
}
|
|
44
37
|
|
|
45
|
-
export interface X509KeyPair { domain: string; cert: Buffer; key: Buffer
|
|
46
|
-
export type NonExtractableKeyPair = { publicKeyBytes: Buffer; privateKey: CryptoKey };
|
|
38
|
+
export interface X509KeyPair { domain: string; cert: Buffer; key: Buffer; }
|
|
47
39
|
|
|
48
40
|
export function getCommonName(cert: Buffer | string) {
|
|
49
41
|
let subject = new crypto.X509Certificate(cert).subject;
|
|
@@ -61,18 +53,14 @@ export function createX509(
|
|
|
61
53
|
keyPair: {
|
|
62
54
|
publicKey: forge.Ed25519PublicKey;
|
|
63
55
|
privateKey: forge.Ed25519PrivateKey;
|
|
64
|
-
} | forge.pki.KeyPair
|
|
56
|
+
} | forge.pki.KeyPair;
|
|
65
57
|
}
|
|
66
|
-
):
|
|
67
|
-
return measureBlock(function createX509()
|
|
58
|
+
): X509KeyPair {
|
|
59
|
+
return measureBlock(function createX509() {
|
|
68
60
|
let { domain, issuer, lifeSpan, keyPair } = config;
|
|
69
61
|
|
|
70
62
|
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
|
-
}
|
|
63
|
+
certObj.publicKey = keyPair.publicKey;
|
|
76
64
|
certObj.serialNumber = "01";
|
|
77
65
|
// Give it 5 minutes before now. If we give it too much time, it can look like the cert is really
|
|
78
66
|
// old, which will trigger various processes to try to get a fresher one (as if it lasts for
|
|
@@ -127,18 +115,7 @@ export function createX509(
|
|
|
127
115
|
certObj.setExtensions(extensions);
|
|
128
116
|
|
|
129
117
|
|
|
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
|
-
}
|
|
118
|
+
measureBlock(function sign() {
|
|
142
119
|
if (issuer === "self") {
|
|
143
120
|
certObj.sign(keyPair.privateKey as any, forge.md.sha256.create());
|
|
144
121
|
} else {
|
|
@@ -146,44 +123,15 @@ export function createX509(
|
|
|
146
123
|
}
|
|
147
124
|
});
|
|
148
125
|
|
|
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();
|
|
126
|
+
return measureBlock(function toPems() {
|
|
127
|
+
return {
|
|
128
|
+
domain,
|
|
129
|
+
cert: Buffer.from(forge.pki.certificateToPem(certObj)),
|
|
130
|
+
key: Buffer.from(privateKeyToPem(keyPair.privateKey)),
|
|
131
|
+
};
|
|
132
|
+
});
|
|
168
133
|
});
|
|
169
134
|
}
|
|
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
135
|
export function privateKeyToPem(buffer: forge.pki.PrivateKey | forge.Ed25519PrivateKey) {
|
|
188
136
|
if ("privateKeyBytes" in buffer) {
|
|
189
137
|
return forge.ed25519.privateKeyToPem(buffer);
|
|
@@ -225,13 +173,8 @@ function isED25519(key: string | Buffer) {
|
|
|
225
173
|
}
|
|
226
174
|
|
|
227
175
|
// EQUIVALENT TO: `crypto.createSign("SHA256").update(JSON.stringify(payload)).sign(keyCert.key, "binary")`
|
|
228
|
-
export const sign = measureWrap(function sign(keyPair: { key: string | Buffer
|
|
176
|
+
export const sign = measureWrap(function sign(keyPair: { key: string | Buffer }, data: unknown): string {
|
|
229
177
|
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
178
|
if (isED25519(keyPair.key)) {
|
|
236
179
|
let privateKey = (forge.pki.ed25519 as any).privateKeyFromPem(keyPair.key.toString());
|
|
237
180
|
return privateKey.sign(dataStr);
|
|
@@ -245,8 +188,20 @@ export const sign = measureWrap(function sign(keyPair: { key: string | Buffer |
|
|
|
245
188
|
|
|
246
189
|
export function verify(cert: string, signature: string, data: unknown) {
|
|
247
190
|
let certObj = parseCert(cert);
|
|
248
|
-
|
|
249
|
-
|
|
191
|
+
let publicKey = certObj.publicKey;
|
|
192
|
+
let dataStr = JSON.stringify(data);
|
|
193
|
+
let verified: boolean;
|
|
194
|
+
if ("publicKeyBytes" in publicKey) {
|
|
195
|
+
// ed25519 verifies the raw message (it hashes internally)
|
|
196
|
+
verified = (publicKey as unknown as { verify(message: string, signature: string): boolean }).verify(dataStr, signature);
|
|
197
|
+
} else {
|
|
198
|
+
// RSA verifies against the digest, matching the digest sign() creates
|
|
199
|
+
const md = forge.md.sha256.create();
|
|
200
|
+
md.update(dataStr);
|
|
201
|
+
verified = (publicKey as forge.pki.rsa.PublicKey).verify(md.digest().bytes(), signature);
|
|
202
|
+
}
|
|
203
|
+
if (!verified) {
|
|
204
|
+
throw new Error(`Signature verification failed. Signature: ${JSON.stringify(signature)} | Data: ${ellipsize(dataStr, 1024)}`);
|
|
250
205
|
}
|
|
251
206
|
}
|
|
252
207
|
|
|
@@ -258,6 +213,27 @@ function normalizeCertToPEM(PEMorDER: string | Buffer): string {
|
|
|
258
213
|
return "-----BEGIN CERTIFICATE-----\n" + PEMorDER + "\n-----END CERTIFICATE-----";
|
|
259
214
|
}
|
|
260
215
|
|
|
216
|
+
// Base32 (RFC 4648, lowercase, unpadded), as domain names are case-insensitive,
|
|
217
|
+
// which rules out base64/hex-with-case
|
|
218
|
+
const base32Alphabet = "abcdefghijklmnopqrstuvwxyz234567";
|
|
219
|
+
function encodeBase32(bytes: Buffer): string {
|
|
220
|
+
let result = "";
|
|
221
|
+
let bitCount = 0;
|
|
222
|
+
let value = 0;
|
|
223
|
+
for (let byte of bytes) {
|
|
224
|
+
value = (value << 8) | byte;
|
|
225
|
+
bitCount += 8;
|
|
226
|
+
while (bitCount >= 5) {
|
|
227
|
+
result += base32Alphabet[(value >>> (bitCount - 5)) & 31];
|
|
228
|
+
bitCount -= 5;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (bitCount > 0) {
|
|
232
|
+
result += base32Alphabet[(value << (5 - bitCount)) & 31];
|
|
233
|
+
}
|
|
234
|
+
return result;
|
|
235
|
+
}
|
|
236
|
+
|
|
261
237
|
function getDomainPartFromPublicKey(publicKey: { publicKeyBytes: Buffer } | forge.pki.KeyPair["publicKey"] | Buffer) {
|
|
262
238
|
let bytes: Buffer;
|
|
263
239
|
if ("publicKeyBytes" in publicKey) {
|
|
@@ -267,7 +243,7 @@ function getDomainPartFromPublicKey(publicKey: { publicKeyBytes: Buffer } | forg
|
|
|
267
243
|
} else {
|
|
268
244
|
bytes = Buffer.from(new Uint32Array((publicKey as any).n.data).buffer);
|
|
269
245
|
}
|
|
270
|
-
return "b" + Buffer.from(sha265.sha256.array(Buffer.from(bytes)))
|
|
246
|
+
return "b" + encodeBase32(Buffer.from(sha265.sha256.array(Buffer.from(bytes)))).slice(0, 20);
|
|
271
247
|
}
|
|
272
248
|
|
|
273
249
|
export function validateCACert(domain: string, cert: string | Buffer) {
|
|
@@ -441,31 +417,6 @@ export function generateTestCA(domain: string) {
|
|
|
441
417
|
|
|
442
418
|
let identityCA = cache((domain: string) => {
|
|
443
419
|
let identityCA = lazy((async (): Promise<X509KeyPair> => {
|
|
444
|
-
if (!isNode()) {
|
|
445
|
-
let store = getBrowserIdentityStore(domain);
|
|
446
|
-
let caCached = await store.get();
|
|
447
|
-
if (!caCached) {
|
|
448
|
-
console.log(`Generating new identity CA (non-extractable browser key)`);
|
|
449
|
-
const keyPair = await generateNonExtractableKeyPair();
|
|
450
|
-
let caPublicKeyPart = getDomainPartFromPublicKey(keyPair.publicKeyBytes);
|
|
451
|
-
let fullDomain = `${caPublicKeyPart}.${domain}`;
|
|
452
|
-
let value = await createX509({ domain: fullDomain, issuer: "self", keyPair, lifeSpan: timeInDay * 365 * 20 });
|
|
453
|
-
caCached = {
|
|
454
|
-
domain: value.domain,
|
|
455
|
-
certB64: value.cert.toString("base64"),
|
|
456
|
-
privateKey: keyPair.privateKey,
|
|
457
|
-
};
|
|
458
|
-
await store.set(caCached);
|
|
459
|
-
}
|
|
460
|
-
let result = {
|
|
461
|
-
domain: caCached.domain,
|
|
462
|
-
cert: Buffer.from(caCached.certB64, "base64"),
|
|
463
|
-
key: caCached.privateKey,
|
|
464
|
-
};
|
|
465
|
-
trustCertificate(result.cert.toString());
|
|
466
|
-
identityCA.set(result);
|
|
467
|
-
return result;
|
|
468
|
-
}
|
|
469
420
|
let identityCACached = getIdentityStore(domain);
|
|
470
421
|
let caCached = await identityCACached.get();
|
|
471
422
|
if (!caCached) {
|
|
@@ -474,12 +425,12 @@ let identityCA = cache((domain: string) => {
|
|
|
474
425
|
let caPublicKeyPart = getDomainPartFromPublicKey(keyPair.publicKey);
|
|
475
426
|
let fullDomain = `${caPublicKeyPart}.${domain}`;
|
|
476
427
|
|
|
477
|
-
let value =
|
|
428
|
+
let value = createX509({ domain: fullDomain, issuer: "self", keyPair, lifeSpan: timeInDay * 365 * 20 });
|
|
478
429
|
|
|
479
430
|
caCached = {
|
|
480
431
|
domain: value.domain,
|
|
481
432
|
certB64: value.cert.toString("base64"),
|
|
482
|
-
keyB64:
|
|
433
|
+
keyB64: value.key.toString("base64"),
|
|
483
434
|
};
|
|
484
435
|
await identityCACached.set(caCached);
|
|
485
436
|
}
|
|
@@ -495,13 +446,6 @@ let identityCA = cache((domain: string) => {
|
|
|
495
446
|
return identityCA;
|
|
496
447
|
});
|
|
497
448
|
|
|
498
|
-
function keyPairKeyToBuffer(key: Buffer | CryptoKey): Buffer {
|
|
499
|
-
if (key instanceof CryptoKey) {
|
|
500
|
-
throw new Error(`Cannot serialize a non-extractable CryptoKey`);
|
|
501
|
-
}
|
|
502
|
-
return key;
|
|
503
|
-
}
|
|
504
|
-
|
|
505
449
|
// IMPORTANT! We do not embed any debug info in this domain. If we did, it would be useful,
|
|
506
450
|
// but... potentally a security vulnerability, as if the debug info (such as a prefix)
|
|
507
451
|
// is used to identify what a certificate is for, it would be easy for an attack to
|
|
@@ -510,22 +454,9 @@ function keyPairKeyToBuffer(key: Buffer | CryptoKey): Buffer {
|
|
|
510
454
|
// (and hopefully stored in a UI, showing IP, time, etc).
|
|
511
455
|
export function createCertFromCA(config: {
|
|
512
456
|
CAKeyPair: X509KeyPair;
|
|
513
|
-
}):
|
|
514
|
-
return measureBlock(function createCertFromCA()
|
|
457
|
+
}): X509KeyPair {
|
|
458
|
+
return measureBlock(function createCertFromCA() {
|
|
515
459
|
let { CAKeyPair } = config;
|
|
516
|
-
if (!isNode()) {
|
|
517
|
-
return (async () => {
|
|
518
|
-
const keyPair = await generateNonExtractableKeyPair();
|
|
519
|
-
let domainKeyPart = getDomainPartFromPublicKey(keyPair.publicKeyBytes);
|
|
520
|
-
let fullDomain = `${domainKeyPart}.${CAKeyPair.domain}`;
|
|
521
|
-
return await createX509({
|
|
522
|
-
domain: fullDomain,
|
|
523
|
-
issuer: CAKeyPair,
|
|
524
|
-
keyPair,
|
|
525
|
-
lifeSpan: timeInDay * 365 * 10,
|
|
526
|
-
});
|
|
527
|
-
})();
|
|
528
|
-
}
|
|
529
460
|
const keyPair = generateKeyPair();
|
|
530
461
|
let domainKeyPart = getDomainPartFromPublicKey(keyPair.publicKey);
|
|
531
462
|
let fullDomain = `${domainKeyPart}.${config.CAKeyPair.domain}`;
|
|
@@ -590,27 +521,12 @@ export function encodeNodeId(parts: NodeIdParts) {
|
|
|
590
521
|
}
|
|
591
522
|
|
|
592
523
|
export async function setIdentityCARaw(domain: string, json: string) {
|
|
524
|
+
let identityCACached = getIdentityStore(domain);
|
|
593
525
|
let obj = JSON.parse(json) as {
|
|
594
526
|
domain: string;
|
|
595
527
|
certB64: string;
|
|
596
528
|
keyB64: string;
|
|
597
529
|
};
|
|
598
|
-
if (!isNode()) {
|
|
599
|
-
// Import into a non-extractable CryptoKey, so once stored the key can't be exfiltrated
|
|
600
|
-
let privateKey = await importEd25519PrivateKey(Buffer.from(obj.keyB64, "base64").toString());
|
|
601
|
-
let ca = {
|
|
602
|
-
domain: obj.domain,
|
|
603
|
-
cert: Buffer.from(obj.certB64, "base64"),
|
|
604
|
-
key: privateKey,
|
|
605
|
-
};
|
|
606
|
-
trustCertificate(ca.cert.toString());
|
|
607
|
-
identityCA(domain).set(ca);
|
|
608
|
-
getThreadKeyCertBase(domain).reset();
|
|
609
|
-
await getBrowserIdentityStore(domain).set({ domain: obj.domain, certB64: obj.certB64, privateKey });
|
|
610
|
-
resetAllNodeCallFactories();
|
|
611
|
-
return;
|
|
612
|
-
}
|
|
613
|
-
let identityCACached = getIdentityStore(domain);
|
|
614
530
|
let ca = {
|
|
615
531
|
domain: obj.domain,
|
|
616
532
|
cert: Buffer.from(obj.certB64, "base64"),
|
|
@@ -623,28 +539,13 @@ export async function setIdentityCARaw(domain: string, json: string) {
|
|
|
623
539
|
resetAllNodeCallFactories();
|
|
624
540
|
}
|
|
625
541
|
|
|
626
|
-
async function importEd25519PrivateKey(pem: string): Promise<CryptoKey> {
|
|
627
|
-
let parsed = forge.ed25519.privateKeyFromPem(pem);
|
|
628
|
-
let bytes = Buffer.from(parsed.privateKeyBytes || parsed);
|
|
629
|
-
let seed = bytes;
|
|
630
|
-
if (seed.length === 64) {
|
|
631
|
-
// NaCl layout is seed || publicKey
|
|
632
|
-
seed = seed.subarray(0, 32);
|
|
633
|
-
}
|
|
634
|
-
if (seed.length !== 32) {
|
|
635
|
-
throw new Error(`Expected a 32 or 64 byte ed25519 private key, was ${bytes.length} bytes`);
|
|
636
|
-
}
|
|
637
|
-
let pkcs8 = Buffer.concat([Buffer.from("302e020100300506032b657004220420", "hex"), seed]);
|
|
638
|
-
return await globalThis.crypto.subtle.importKey("pkcs8", pkcs8, "Ed25519", false, ["sign"]);
|
|
639
|
-
}
|
|
640
|
-
|
|
641
542
|
export async function loadIdentityCA(domain: string) {
|
|
642
543
|
await identityCA(domain)();
|
|
643
544
|
}
|
|
644
545
|
export function getIdentityCA(domain: string): X509KeyPair {
|
|
645
546
|
let value = identityCA(domain)();
|
|
646
547
|
if (value instanceof Promise) {
|
|
647
|
-
throw new Error(
|
|
548
|
+
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)}))`);
|
|
648
549
|
}
|
|
649
550
|
return value;
|
|
650
551
|
}
|
|
@@ -660,11 +561,7 @@ export function getOwnMachineId(domain: string) {
|
|
|
660
561
|
return getMachineId(getIdentityCA(domain).domain, domain);
|
|
661
562
|
}
|
|
662
563
|
export function getOwnThreadId(domain: string) {
|
|
663
|
-
|
|
664
|
-
if (threadKeyCert instanceof Promise) {
|
|
665
|
-
throw new Error("Thread key cert is not yet loaded. Await getThreadKeyCert() in your startup before accessing the threadId");
|
|
666
|
-
}
|
|
667
|
-
return decodeNodeIdAssert(threadKeyCert.domain, domain).threadId;
|
|
564
|
+
return decodeNodeIdAssert(getThreadKeyCert(domain).domain, domain).threadId;
|
|
668
565
|
}
|
|
669
566
|
|
|
670
567
|
/** Part of the machineId comes from the publicKey, so we can use it to verify */
|
|
@@ -1,8 +1,4 @@
|
|
|
1
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
2
|
export declare function getKeyStore<T>(appName: string, key: string): {
|
|
7
3
|
get(): MaybePromise<T | undefined>;
|
|
8
4
|
set(value: T | null): MaybePromise<void>;
|
|
@@ -4,42 +4,6 @@ import os from "os";
|
|
|
4
4
|
import { MaybePromise } from "socket-function/src/types";
|
|
5
5
|
import { cache } from "socket-function/src/caching";
|
|
6
6
|
|
|
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
7
|
export function getKeyStore<T>(appName: string, key: string): {
|
|
44
8
|
get(): MaybePromise<T | undefined>;
|
|
45
9
|
set(value: T | null): MaybePromise<void>;
|