sliftutils 1.5.5 → 1.5.6
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 +14 -12
- package/misc/https/certs.d.ts +7 -10
- package/misc/https/certs.ts +43 -145
- package/misc/https/node-forge-ed25519.d.ts +7 -3
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -71,7 +71,7 @@ declare module "sliftutils/misc/https/certs" {
|
|
|
71
71
|
/// <reference types="node" />
|
|
72
72
|
import * as forge from "node-forge";
|
|
73
73
|
export declare const CA_NOT_FOUND_ERROR = "18aa7318-f88f-4d2d-b41f-3daf4a433827";
|
|
74
|
-
export declare const identityStorageKey = "
|
|
74
|
+
export declare const identityStorageKey = "machineCA_14";
|
|
75
75
|
export type IdentityStorageType = {
|
|
76
76
|
domain: string;
|
|
77
77
|
certB64: string;
|
|
@@ -90,9 +90,9 @@ declare module "sliftutils/misc/https/certs" {
|
|
|
90
90
|
keyPair: {
|
|
91
91
|
publicKey: forge.Ed25519PublicKey;
|
|
92
92
|
privateKey: forge.Ed25519PrivateKey;
|
|
93
|
-
}
|
|
93
|
+
};
|
|
94
94
|
}): X509KeyPair;
|
|
95
|
-
export declare function privateKeyToPem(
|
|
95
|
+
export declare function privateKeyToPem(key: forge.Ed25519PrivateKey): string;
|
|
96
96
|
export declare function parseCert(PEMorDER: string | Buffer): forge.pki.Certificate;
|
|
97
97
|
export declare function getPublicIdentifier(PEMorDER: string | Buffer): Buffer;
|
|
98
98
|
export declare const sign: (keyPair: {
|
|
@@ -101,8 +101,10 @@ declare module "sliftutils/misc/https/certs" {
|
|
|
101
101
|
export declare function verify(cert: string, signature: string, data: unknown): void;
|
|
102
102
|
export declare function validateCACert(domain: string, cert: string | Buffer): void;
|
|
103
103
|
export declare function validateCertificate(domain: string, cert: Buffer | string, issuerCert: Buffer | string): void;
|
|
104
|
-
export declare function generateKeyPair():
|
|
105
|
-
|
|
104
|
+
export declare function generateKeyPair(): {
|
|
105
|
+
publicKey: forge.Ed25519PublicKey;
|
|
106
|
+
privateKey: forge.Ed25519PrivateKey;
|
|
107
|
+
};
|
|
106
108
|
export declare function generateTestCA(domain: string): X509KeyPair;
|
|
107
109
|
export declare function createCertFromCA(config: {
|
|
108
110
|
CAKeyPair: X509KeyPair;
|
|
@@ -129,11 +131,6 @@ declare module "sliftutils/misc/https/certs" {
|
|
|
129
131
|
publicKey: Buffer;
|
|
130
132
|
}): boolean;
|
|
131
133
|
export declare function getThreadKeyCert(domain: string): X509KeyPair;
|
|
132
|
-
export declare const createTestBrowserKeyCert: {
|
|
133
|
-
(): Promise<X509KeyPair>;
|
|
134
|
-
reset(): void;
|
|
135
|
-
set(newValue: Promise<X509KeyPair>): void;
|
|
136
|
-
};
|
|
137
134
|
export declare function getOwnNodeId(): string;
|
|
138
135
|
export declare function getOwnNodeIdAllowUndefined(): string;
|
|
139
136
|
|
|
@@ -201,10 +198,14 @@ declare module "sliftutils/misc/https/node-forge-ed25519" {
|
|
|
201
198
|
declare module "node-forge" {
|
|
202
199
|
declare type Ed25519PublicKey = {
|
|
203
200
|
publicKeyBytes: Buffer;
|
|
204
|
-
|
|
201
|
+
keyType: string;
|
|
202
|
+
verify(message: string | Buffer, signature: string): boolean;
|
|
203
|
+
};
|
|
205
204
|
declare type Ed25519PrivateKey = {
|
|
206
205
|
privateKeyBytes: Buffer;
|
|
207
|
-
|
|
206
|
+
keyType: string;
|
|
207
|
+
sign(message: string | Buffer): string;
|
|
208
|
+
};
|
|
208
209
|
class ed25519 {
|
|
209
210
|
static generateKeyPair(): { publicKey: Ed25519PublicKey, privateKey: Ed25519PrivateKey };
|
|
210
211
|
static privateKeyToPem(key: Ed25519PrivateKey): string;
|
|
@@ -213,6 +214,7 @@ declare module "sliftutils/misc/https/node-forge-ed25519" {
|
|
|
213
214
|
static publicKeyFromPem(pem: string): Ed25519PublicKey;
|
|
214
215
|
}
|
|
215
216
|
}
|
|
217
|
+
|
|
216
218
|
}
|
|
217
219
|
|
|
218
220
|
declare module "sliftutils/misc/https/persistentLocalStorage" {
|
package/misc/https/certs.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
/// <reference types="node" />
|
|
5
5
|
import * as forge from "node-forge";
|
|
6
6
|
export declare const CA_NOT_FOUND_ERROR = "18aa7318-f88f-4d2d-b41f-3daf4a433827";
|
|
7
|
-
export declare const identityStorageKey = "
|
|
7
|
+
export declare const identityStorageKey = "machineCA_14";
|
|
8
8
|
export type IdentityStorageType = {
|
|
9
9
|
domain: string;
|
|
10
10
|
certB64: string;
|
|
@@ -23,9 +23,9 @@ export declare function createX509(config: {
|
|
|
23
23
|
keyPair: {
|
|
24
24
|
publicKey: forge.Ed25519PublicKey;
|
|
25
25
|
privateKey: forge.Ed25519PrivateKey;
|
|
26
|
-
}
|
|
26
|
+
};
|
|
27
27
|
}): X509KeyPair;
|
|
28
|
-
export declare function privateKeyToPem(
|
|
28
|
+
export declare function privateKeyToPem(key: forge.Ed25519PrivateKey): string;
|
|
29
29
|
export declare function parseCert(PEMorDER: string | Buffer): forge.pki.Certificate;
|
|
30
30
|
export declare function getPublicIdentifier(PEMorDER: string | Buffer): Buffer;
|
|
31
31
|
export declare const sign: (keyPair: {
|
|
@@ -34,8 +34,10 @@ export declare const sign: (keyPair: {
|
|
|
34
34
|
export declare function verify(cert: string, signature: string, data: unknown): void;
|
|
35
35
|
export declare function validateCACert(domain: string, cert: string | Buffer): void;
|
|
36
36
|
export declare function validateCertificate(domain: string, cert: Buffer | string, issuerCert: Buffer | string): void;
|
|
37
|
-
export declare function generateKeyPair():
|
|
38
|
-
|
|
37
|
+
export declare function generateKeyPair(): {
|
|
38
|
+
publicKey: forge.Ed25519PublicKey;
|
|
39
|
+
privateKey: forge.Ed25519PrivateKey;
|
|
40
|
+
};
|
|
39
41
|
export declare function generateTestCA(domain: string): X509KeyPair;
|
|
40
42
|
export declare function createCertFromCA(config: {
|
|
41
43
|
CAKeyPair: X509KeyPair;
|
|
@@ -62,10 +64,5 @@ export declare function verifyMachineIdForPublicKey(config: {
|
|
|
62
64
|
publicKey: Buffer;
|
|
63
65
|
}): boolean;
|
|
64
66
|
export declare function getThreadKeyCert(domain: string): X509KeyPair;
|
|
65
|
-
export declare const createTestBrowserKeyCert: {
|
|
66
|
-
(): Promise<X509KeyPair>;
|
|
67
|
-
reset(): void;
|
|
68
|
-
set(newValue: Promise<X509KeyPair>): void;
|
|
69
|
-
};
|
|
70
67
|
export declare function getOwnNodeId(): string;
|
|
71
68
|
export declare function getOwnNodeIdAllowUndefined(): string;
|
package/misc/https/certs.ts
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module.allowclient = true;
|
|
4
4
|
|
|
5
|
+
// NOTE: We can't use crypto.subtle (non-extractable CryptoKeys) for our keys, because subtle is asynchronous, and everything here (key generation, cert creation, signing) must be available synchronously. The only real benefit of subtle is that a cross-site scripting attack can't exfiltrate the key. Which, while nice, is of minor benefit, as the cross-site script can already do quite a bit of damage anyway with access. And if that happens, the first thing the user is probably going to do is reset all their credentials, which solves the case of the key being exfiltrated anyway (by telling the server to stop trusting all identities).
|
|
6
|
+
// NOTE: We are just not going to support HTTPS browser certs. Our code is purely for identification, and so it only supports ED25519.
|
|
7
|
+
|
|
5
8
|
// https://www.rfc-editor.org/rfc/rfc5280#page-42
|
|
6
9
|
|
|
7
10
|
import { setFlag } from "socket-function/require/compileFlags";
|
|
@@ -27,7 +30,7 @@ const timeInDay = 1000 * 60 * 60 * 24;
|
|
|
27
30
|
|
|
28
31
|
export const CA_NOT_FOUND_ERROR = "18aa7318-f88f-4d2d-b41f-3daf4a433827";
|
|
29
32
|
|
|
30
|
-
export const identityStorageKey = "
|
|
33
|
+
export const identityStorageKey = "machineCA_14";
|
|
31
34
|
export type IdentityStorageType = { domain: string; certB64: string; keyB64: string };
|
|
32
35
|
|
|
33
36
|
function getIdentityStore(domain: string) {
|
|
@@ -52,19 +55,16 @@ export function createX509(
|
|
|
52
55
|
keyPair: {
|
|
53
56
|
publicKey: forge.Ed25519PublicKey;
|
|
54
57
|
privateKey: forge.Ed25519PrivateKey;
|
|
55
|
-
}
|
|
58
|
+
};
|
|
56
59
|
}
|
|
57
60
|
): X509KeyPair {
|
|
58
61
|
return measureBlock(function createX509() {
|
|
59
62
|
let { domain, issuer, lifeSpan, keyPair } = config;
|
|
60
63
|
|
|
61
64
|
let certObj = forge.pki.createCertificate();
|
|
62
|
-
certObj.publicKey = keyPair.publicKey;
|
|
65
|
+
certObj.publicKey = keyPair.publicKey as unknown as forge.pki.PublicKey;
|
|
63
66
|
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...)
|
|
67
|
+
// Give it 5 minutes before now. If we give it too much time, it can look like the cert is really old, which will trigger various processes to try to get a fresher one (as if it lasts for 1 hour, but we set notBefore to 1 month ago, it looks 1 month old, and so almost expired, when it isn't...)
|
|
68
68
|
certObj.validity.notBefore = new Date(Date.now() - 1000 * 60 * 5);
|
|
69
69
|
certObj.validity.notAfter = new Date(Date.now() + lifeSpan);
|
|
70
70
|
|
|
@@ -94,15 +94,10 @@ export function createX509(
|
|
|
94
94
|
{ type: 2, value: domain },
|
|
95
95
|
{ type: 2, value: "*." + domain },
|
|
96
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" }
|
|
97
|
+
// NOTE: No longer allow 127.0.0.1 ({ type: 7, ip: "127.0.0.1" }), to make this more secure. We might enable this behavior behind a flag, for development.
|
|
100
98
|
]
|
|
101
99
|
},
|
|
102
|
-
// NOTE: nameConstraints require our forked node-forge:
|
|
103
|
-
// "node-forge": "https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6",
|
|
104
|
-
// Chrome ignores them (https://bugs.chromium.org/p/chromium/issues/detail?id=1072083),
|
|
105
|
-
// but our own validation (validateCACert/validateCertificate) enforces them.
|
|
100
|
+
// NOTE: nameConstraints require our forked node-forge ("node-forge": "https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6"). Chrome ignores them (https://bugs.chromium.org/p/chromium/issues/detail?id=1072083), but our own validation (validateCACert/validateCertificate) enforces them.
|
|
106
101
|
{
|
|
107
102
|
name: "nameConstraints",
|
|
108
103
|
permittedSubtrees: [
|
|
@@ -116,9 +111,9 @@ export function createX509(
|
|
|
116
111
|
|
|
117
112
|
measureBlock(function sign() {
|
|
118
113
|
if (issuer === "self") {
|
|
119
|
-
certObj.sign(keyPair.privateKey as any
|
|
114
|
+
certObj.sign(keyPair.privateKey as any);
|
|
120
115
|
} else {
|
|
121
|
-
certObj.sign(privateKeyFromPem(issuer.key.toString()) as any
|
|
116
|
+
certObj.sign(forge.ed25519.privateKeyFromPem(issuer.key.toString()) as any);
|
|
122
117
|
}
|
|
123
118
|
});
|
|
124
119
|
|
|
@@ -131,75 +126,37 @@ export function createX509(
|
|
|
131
126
|
});
|
|
132
127
|
});
|
|
133
128
|
}
|
|
134
|
-
export function privateKeyToPem(
|
|
135
|
-
|
|
136
|
-
return forge.ed25519.privateKeyToPem(buffer);
|
|
137
|
-
}
|
|
138
|
-
return forge.pki.privateKeyToPem(buffer);
|
|
139
|
-
}
|
|
140
|
-
function privateKeyFromPem(pem: string) {
|
|
141
|
-
// We want to guess the type correctly, as caught exceptions make debugging annoying
|
|
142
|
-
if (pem.length < 200) {
|
|
143
|
-
try {
|
|
144
|
-
return forge.ed25519.privateKeyFromPem(pem);
|
|
145
|
-
} catch { }
|
|
146
|
-
}
|
|
147
|
-
try {
|
|
148
|
-
return forge.pki.privateKeyFromPem(pem);
|
|
149
|
-
} catch {
|
|
150
|
-
return forge.ed25519.privateKeyFromPem(pem);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
function publicKeyFromCert(cert: string) {
|
|
154
|
-
return parseCert(cert).publicKey;
|
|
129
|
+
export function privateKeyToPem(key: forge.Ed25519PrivateKey) {
|
|
130
|
+
return forge.ed25519.privateKeyToPem(key);
|
|
155
131
|
}
|
|
156
132
|
export function parseCert(PEMorDER: string | Buffer) {
|
|
157
133
|
return forge.pki.certificateFromPem(normalizeCertToPEM(PEMorDER));
|
|
158
134
|
}
|
|
159
135
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if ("publicKeyBytes" in publicKey) {
|
|
165
|
-
return Buffer.from(publicKey.publicKeyBytes as any);
|
|
136
|
+
function getED25519PublicKey(certParsed: forge.pki.Certificate): forge.Ed25519PublicKey {
|
|
137
|
+
let publicKey = certParsed.publicKey;
|
|
138
|
+
if (!("publicKeyBytes" in publicKey)) {
|
|
139
|
+
throw new Error(`Only ED25519 certificates are supported, the certificate public key is not ED25519 (subject: ${certParsed.subject.getField("CN")?.value})`);
|
|
166
140
|
}
|
|
167
|
-
return
|
|
141
|
+
return publicKey as unknown as forge.Ed25519PublicKey;
|
|
168
142
|
}
|
|
169
143
|
|
|
170
|
-
|
|
171
|
-
|
|
144
|
+
// Gets a unique value to represent the public key
|
|
145
|
+
export function getPublicIdentifier(PEMorDER: string | Buffer): Buffer {
|
|
146
|
+
return Buffer.from(getED25519PublicKey(parseCert(PEMorDER)).publicKeyBytes);
|
|
172
147
|
}
|
|
173
148
|
|
|
174
|
-
// EQUIVALENT TO: `crypto.createSign("SHA256").update(JSON.stringify(payload)).sign(keyCert.key, "binary")`
|
|
175
149
|
export const sign = measureWrap(function sign(keyPair: { key: string | Buffer }, data: unknown): string {
|
|
176
150
|
let dataStr = JSON.stringify(data);
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
return privateKey.sign(dataStr);
|
|
180
|
-
} else {
|
|
181
|
-
let privateKey = forge.pki.privateKeyFromPem(keyPair.key.toString());
|
|
182
|
-
const md = forge.md.sha256.create();
|
|
183
|
-
md.update(dataStr);
|
|
184
|
-
return privateKey.sign(md);
|
|
185
|
-
}
|
|
151
|
+
let privateKey = forge.ed25519.privateKeyFromPem(keyPair.key.toString());
|
|
152
|
+
return privateKey.sign(dataStr);
|
|
186
153
|
});
|
|
187
154
|
|
|
188
155
|
export function verify(cert: string, signature: string, data: unknown) {
|
|
189
156
|
let certObj = parseCert(cert);
|
|
190
|
-
let publicKey = certObj
|
|
157
|
+
let publicKey = getED25519PublicKey(certObj);
|
|
191
158
|
let dataStr = JSON.stringify(data);
|
|
192
|
-
|
|
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) {
|
|
159
|
+
if (!publicKey.verify(dataStr, signature)) {
|
|
203
160
|
throw new Error(`Signature verification failed. Signature: ${JSON.stringify(signature)} | Data: ${ellipsize(dataStr, 1024)}`);
|
|
204
161
|
}
|
|
205
162
|
}
|
|
@@ -212,8 +169,7 @@ function normalizeCertToPEM(PEMorDER: string | Buffer): string {
|
|
|
212
169
|
return "-----BEGIN CERTIFICATE-----\n" + PEMorDER + "\n-----END CERTIFICATE-----";
|
|
213
170
|
}
|
|
214
171
|
|
|
215
|
-
// Base32 (RFC 4648, lowercase, unpadded), as domain names are case-insensitive,
|
|
216
|
-
// which rules out base64/hex-with-case
|
|
172
|
+
// Base32 (RFC 4648, lowercase, unpadded), as domain names are case-insensitive, which rules out base64/hex-with-case
|
|
217
173
|
const base32Alphabet = "abcdefghijklmnopqrstuvwxyz234567";
|
|
218
174
|
function encodeBase32(bytes: Buffer): string {
|
|
219
175
|
let result = "";
|
|
@@ -233,14 +189,12 @@ function encodeBase32(bytes: Buffer): string {
|
|
|
233
189
|
return result;
|
|
234
190
|
}
|
|
235
191
|
|
|
236
|
-
function getDomainPartFromPublicKey(publicKey: { publicKeyBytes: Buffer } |
|
|
192
|
+
function getDomainPartFromPublicKey(publicKey: { publicKeyBytes: Buffer } | Buffer) {
|
|
237
193
|
let bytes: Buffer;
|
|
238
194
|
if ("publicKeyBytes" in publicKey) {
|
|
239
195
|
bytes = publicKey.publicKeyBytes;
|
|
240
|
-
} else if (publicKey instanceof Buffer) {
|
|
241
|
-
bytes = publicKey;
|
|
242
196
|
} else {
|
|
243
|
-
bytes =
|
|
197
|
+
bytes = publicKey;
|
|
244
198
|
}
|
|
245
199
|
return "b" + encodeBase32(Buffer.from(sha265.sha256.array(Buffer.from(bytes)))).slice(0, 20);
|
|
246
200
|
}
|
|
@@ -255,9 +209,7 @@ export function validateCACert(domain: string, cert: string | Buffer) {
|
|
|
255
209
|
|
|
256
210
|
let rootDomainParsed = [domainParts.shift(), domainParts.shift()].reverse().join(".");
|
|
257
211
|
if (rootDomainParsed !== domain) {
|
|
258
|
-
// This is important, as our trust store contains more then just OUR certificates,
|
|
259
|
-
// so if we allow any domains then real domains can impersonate anyone! It has to
|
|
260
|
-
// be one OUR domain to be trusted!
|
|
212
|
+
// This is important, as our trust store contains more then just OUR certificates, so if we allow any domains then real domains can impersonate anyone! It has to be OUR domain to be trusted!
|
|
261
213
|
throw new Error(`Certificate root domain should be ${domain}, but is ${rootDomainParsed}`);
|
|
262
214
|
}
|
|
263
215
|
// TODO: Maybe just skip if it isn't a hash string?
|
|
@@ -266,7 +218,7 @@ export function validateCACert(domain: string, cert: string | Buffer) {
|
|
|
266
218
|
}
|
|
267
219
|
|
|
268
220
|
let certExpectedPublicKeyPart = (domainParts.shift() || "").split("-").slice(-1)[0];
|
|
269
|
-
let certActualPublicKeyPart = getDomainPartFromPublicKey(certParsed
|
|
221
|
+
let certActualPublicKeyPart = getDomainPartFromPublicKey(getED25519PublicKey(certParsed));
|
|
270
222
|
if (certExpectedPublicKeyPart !== certActualPublicKeyPart) {
|
|
271
223
|
throw new Error(`Certificate public key in the url is ${certExpectedPublicKeyPart}, but in the cert is ${certActualPublicKeyPart}`);
|
|
272
224
|
}
|
|
@@ -281,8 +233,7 @@ export function validateCACert(domain: string, cert: string | Buffer) {
|
|
|
281
233
|
throw new Error(`Certificate must have nameConstraints.permittedSubtrees`);
|
|
282
234
|
}
|
|
283
235
|
let subtreeValues = subtrees.map((x: any) => x.value);
|
|
284
|
-
// Ignore localhostDomain, as it can always safely be allowed (the same machine
|
|
285
|
-
// is always allowed).
|
|
236
|
+
// Ignore localhostDomain, as it can always safely be allowed (the same machine is always allowed).
|
|
286
237
|
subtreeValues = subtreeValues.filter((x: string) => x !== localhostDomain);
|
|
287
238
|
if (subtreeValues.length !== 1 || subtreeValues[0] !== subject) {
|
|
288
239
|
throw new Error(`Certificate must have a single constrained domain (had ${JSON.stringify(subtreeValues)})`);
|
|
@@ -312,14 +263,14 @@ export function validateCertificate(domain: string, cert: Buffer | string, issue
|
|
|
312
263
|
let issuerCertParsed = parseCert(issuerCert);
|
|
313
264
|
|
|
314
265
|
let issuerExpectedPublicKeyPart = domainParts.shift() || "";
|
|
315
|
-
let issuerActualPublicKeyPart = getDomainPartFromPublicKey(issuerCertParsed
|
|
266
|
+
let issuerActualPublicKeyPart = getDomainPartFromPublicKey(getED25519PublicKey(issuerCertParsed));
|
|
316
267
|
if (issuerExpectedPublicKeyPart !== issuerActualPublicKeyPart) {
|
|
317
268
|
throw new Error(`Issuer public key in the url is ${issuerExpectedPublicKeyPart}, but in the cert is ${issuerActualPublicKeyPart}`);
|
|
318
269
|
}
|
|
319
270
|
|
|
320
271
|
// Take the last part
|
|
321
272
|
let certExpectedPublicKeyPart = domainParts.shift() || "";
|
|
322
|
-
let certActualPublicKeyPart = getDomainPartFromPublicKey(certParsed
|
|
273
|
+
let certActualPublicKeyPart = getDomainPartFromPublicKey(getED25519PublicKey(certParsed));
|
|
323
274
|
if (certExpectedPublicKeyPart !== certActualPublicKeyPart) {
|
|
324
275
|
throw new Error(`Certificate public key in the url is ${certExpectedPublicKeyPart}, but in the cert is ${certActualPublicKeyPart}`);
|
|
325
276
|
}
|
|
@@ -334,8 +285,7 @@ export function validateCertificate(domain: string, cert: Buffer | string, issue
|
|
|
334
285
|
throw new Error(`CA must have nameConstraints.permittedSubtrees`);
|
|
335
286
|
}
|
|
336
287
|
let subtreeValues = subtrees.map((x: any) => x.value);
|
|
337
|
-
// Ignore localhostDomain, as it can always safely be allowed (the same machine
|
|
338
|
-
// is always allowed).
|
|
288
|
+
// Ignore localhostDomain, as it can always safely be allowed (the same machine is always allowed).
|
|
339
289
|
subtreeValues = subtreeValues.filter((x: string) => x !== localhostDomain);
|
|
340
290
|
if (subtreeValues.length !== 1) {
|
|
341
291
|
throw new Error(`CA must have a single constrained domain (had ${JSON.stringify(subtreeValues)})`);
|
|
@@ -367,16 +317,7 @@ function validateAltNames(certParsed: forge.pki.Certificate, subject: string) {
|
|
|
367
317
|
!(
|
|
368
318
|
x === subject
|
|
369
319
|
|| x.endsWith("." + subject)
|
|
370
|
-
//
|
|
371
|
-
// and even to generate a real cert, so, we should jsut do that, and keep it secure.
|
|
372
|
-
// If we need this for development we can put it behind a flag, so non-development
|
|
373
|
-
// instances are still secure.
|
|
374
|
-
// // Also allow 127.0.0.1, for local testing, for now?
|
|
375
|
-
// // - This might be insecure if these are trusted by the browser,
|
|
376
|
-
// // as then anyone that stores any cookies in this ip can have
|
|
377
|
-
// // the cookies stolen. But... if this is just cross-server...
|
|
378
|
-
// // I don't see how this could cause a security vulnerability.
|
|
379
|
-
// || x === Buffer.from([127, 0, 0, 1]).toString()
|
|
320
|
+
// NOTE: We don't allow 127.0.0.1 (|| x === Buffer.from([127, 0, 0, 1]).toString()), because it is so easy to publish a 127.0.0.1 A record, and even to generate a real cert, so we should just do that, and keep it secure. If we need this for development we can put it behind a flag, so non-development instances are still secure.
|
|
380
321
|
)
|
|
381
322
|
)
|
|
382
323
|
) {
|
|
@@ -385,29 +326,10 @@ function validateAltNames(certParsed: forge.pki.Certificate, subject: string) {
|
|
|
385
326
|
}
|
|
386
327
|
|
|
387
328
|
|
|
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).
|
|
396
329
|
export function generateKeyPair() {
|
|
397
330
|
return measureBlock(function generateKeyPair() {
|
|
398
|
-
// NOTE: We use ED25519 because it can
|
|
399
|
-
|
|
400
|
-
// NOTE: ED25519 doens't have great support in browsers, but we shouldn't need self signed certificates
|
|
401
|
-
// in the browser anyway.
|
|
402
|
-
// - https://security.stackexchange.com/a/236943/282367
|
|
403
|
-
//let keyPair = forge.ed25519.generateKeyPair();
|
|
404
|
-
let keyPair = forge.pki.rsa.generateKeyPair();
|
|
405
|
-
return keyPair;
|
|
406
|
-
});
|
|
407
|
-
}
|
|
408
|
-
export function generateRSAKeyPair() {
|
|
409
|
-
return measureBlock(function generateKeyPair() {
|
|
410
|
-
return forge.pki.rsa.generateKeyPair();
|
|
331
|
+
// NOTE: We use ED25519 because it can generate keys about 10X faster than RSA (which is still slow, ~6ms on my machine, so we DEFINITELY don't want it to be 10X slower!) - https://security.stackexchange.com/a/236943/282367
|
|
332
|
+
return forge.ed25519.generateKeyPair();
|
|
411
333
|
});
|
|
412
334
|
}
|
|
413
335
|
|
|
@@ -415,10 +337,6 @@ export function generateTestCA(domain: string) {
|
|
|
415
337
|
const keyPair = generateKeyPair();
|
|
416
338
|
let caPublicKeyPart = getDomainPartFromPublicKey(keyPair.publicKey);
|
|
417
339
|
let fullDomain = `${caPublicKeyPart}.${domain}`;
|
|
418
|
-
if (!isNode()) {
|
|
419
|
-
fullDomain = `${caPublicKeyPart}.${domain}`;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
340
|
return createX509({ domain: fullDomain, issuer: "self", keyPair, lifeSpan: timeInDay * 365 * 20 });
|
|
423
341
|
}
|
|
424
342
|
|
|
@@ -449,12 +367,7 @@ let identityCA = cache((domain: string) => lazy((): X509KeyPair => {
|
|
|
449
367
|
return result;
|
|
450
368
|
}));
|
|
451
369
|
|
|
452
|
-
// IMPORTANT! We do not embed any debug info in this domain. If we did, it would be useful,
|
|
453
|
-
// but... potentally a security vulnerability, as if the debug info (such as a prefix)
|
|
454
|
-
// is used to identify what a certificate is for, it would be easy for an attack to
|
|
455
|
-
// forge this (as the debug info won't be secured). So it is much better to keep
|
|
456
|
-
// the certificate opaque, and then require any metadata to be actually vetted
|
|
457
|
-
// (and hopefully stored in a UI, showing IP, time, etc).
|
|
370
|
+
// IMPORTANT! We do not embed any debug info in this domain. If we did, it would be useful, but... potentally a security vulnerability, as if the debug info (such as a prefix) is used to identify what a certificate is for, it would be easy for an attack to forge this (as the debug info won't be secured). So it is much better to keep the certificate opaque, and then require any metadata to be actually vetted (and hopefully stored in a UI, showing IP, time, etc).
|
|
458
371
|
export function createCertFromCA(config: {
|
|
459
372
|
CAKeyPair: X509KeyPair;
|
|
460
373
|
}): X509KeyPair {
|
|
@@ -488,11 +401,7 @@ export function decodeNodeId(nodeId: string, domain: string, allowMissingThreadI
|
|
|
488
401
|
return undefined;
|
|
489
402
|
}
|
|
490
403
|
let parts = locationObj.address.split(".");
|
|
491
|
-
// NOTE: We have to only allow localhost domains on our own domain, as the underlying domain
|
|
492
|
-
// gets stripped when we're looking at the machineId. So if we allowed localhost domains on
|
|
493
|
-
// other domains, a server could trick us into connecting to it, and then once the connection
|
|
494
|
-
// is established, it could talk back and we would think it has a localhost machineId, which
|
|
495
|
-
// is implicitly trusted, which would then give it access to everything.
|
|
404
|
+
// NOTE: We have to only allow localhost domains on our own domain, as the underlying domain gets stripped when we're looking at the machineId. So if we allowed localhost domains on other domains, a server could trick us into connecting to it, and then once the connection is established, it could talk back and we would think it has a localhost machineId, which is implicitly trusted, which would then give it access to everything.
|
|
496
405
|
if (locationObj.address === `127-0-0-1.${domain}` && nodeId.includes(":")) {
|
|
497
406
|
return {
|
|
498
407
|
threadId: "",
|
|
@@ -542,9 +451,7 @@ export async function setIdentityCARaw(domain: string, json: string) {
|
|
|
542
451
|
resetAllNodeCallFactories();
|
|
543
452
|
}
|
|
544
453
|
|
|
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.
|
|
454
|
+
// NOTE: The identity CA is available synchronously (storage is fs/localStorage, both synchronous), so this only exists for backwards compatibility with startup code that awaits it.
|
|
548
455
|
export async function loadIdentityCA(domain: string) {
|
|
549
456
|
identityCA(domain)();
|
|
550
457
|
}
|
|
@@ -552,8 +459,7 @@ export function getIdentityCA(domain: string): X509KeyPair {
|
|
|
552
459
|
return identityCA(domain)();
|
|
553
460
|
}
|
|
554
461
|
|
|
555
|
-
// TODO: Replace this with a database, so it is easy for us to trust CAs
|
|
556
|
-
// cross machine, and even have multiple users, etc, etc.
|
|
462
|
+
// TODO: Replace this with a database, so it is easy for us to trust CAs cross machine, and even have multiple users, etc, etc.
|
|
557
463
|
export function getIdentityCAPromise(domain: string): X509KeyPair {
|
|
558
464
|
return identityCA(domain)();
|
|
559
465
|
}
|
|
@@ -578,11 +484,8 @@ export function verifyMachineIdForPublicKey(config: {
|
|
|
578
484
|
}
|
|
579
485
|
|
|
580
486
|
// NOTE: We don't have a cache per CA, as... the CA should be set first
|
|
581
|
-
//
|
|
582
|
-
// TODO: Regenerate certificates after enough time (as thread certs should be relatively short lived,
|
|
583
|
-
// so it is plausible for them to expire)
|
|
584
|
-
// - We will also need to provide a callback so that users of the cert can update the cert they
|
|
585
|
-
// are using as well.
|
|
487
|
+
// TODO: Maybe throw if they try to change the CA after they generate any certificates?
|
|
488
|
+
// TODO: Regenerate certificates after enough time (as thread certs should be relatively short lived, so it is plausible for them to expire). We will also need to provide a callback so that users of the cert can update the cert they are using as well.
|
|
586
489
|
export function getThreadKeyCert(domain: string) {
|
|
587
490
|
return getThreadKeyCertBase(domain)();
|
|
588
491
|
}
|
|
@@ -591,11 +494,6 @@ const getThreadKeyCertBase = cache((domain: string) => lazy(() => {
|
|
|
591
494
|
return createCertFromCA({ CAKeyPair: ca });
|
|
592
495
|
}));
|
|
593
496
|
|
|
594
|
-
export const createTestBrowserKeyCert = lazy(async () => {
|
|
595
|
-
let keyPair = generateRSAKeyPair();
|
|
596
|
-
return await createX509({ domain: "test", issuer: "self", keyPair, lifeSpan: timeInDay * 365 * 20 });
|
|
597
|
-
});
|
|
598
|
-
|
|
599
497
|
export function getOwnNodeId(): string {
|
|
600
498
|
let nodeId = SocketFunction.mountedNodeId;
|
|
601
499
|
if (!nodeId) {
|
|
@@ -3,10 +3,14 @@
|
|
|
3
3
|
declare module "node-forge" {
|
|
4
4
|
declare type Ed25519PublicKey = {
|
|
5
5
|
publicKeyBytes: Buffer;
|
|
6
|
-
|
|
6
|
+
keyType: string;
|
|
7
|
+
verify(message: string | Buffer, signature: string): boolean;
|
|
8
|
+
};
|
|
7
9
|
declare type Ed25519PrivateKey = {
|
|
8
10
|
privateKeyBytes: Buffer;
|
|
9
|
-
|
|
11
|
+
keyType: string;
|
|
12
|
+
sign(message: string | Buffer): string;
|
|
13
|
+
};
|
|
10
14
|
class ed25519 {
|
|
11
15
|
static generateKeyPair(): { publicKey: Ed25519PublicKey, privateKey: Ed25519PrivateKey };
|
|
12
16
|
static privateKeyToPem(key: Ed25519PrivateKey): string;
|
|
@@ -14,4 +18,4 @@ declare module "node-forge" {
|
|
|
14
18
|
static publicKeyToPem(key: Ed25519PublicKey): string;
|
|
15
19
|
static publicKeyFromPem(pem: string): Ed25519PublicKey;
|
|
16
20
|
}
|
|
17
|
-
}
|
|
21
|
+
}
|