@hackthedev/dsync-sign 1.0.3 → 1.0.5
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.mjs +53 -17
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {promises as fs} from "fs";
|
|
2
2
|
import crypto from "crypto";
|
|
3
3
|
|
|
4
4
|
export class dSyncSign {
|
|
@@ -11,6 +11,7 @@ export class dSyncSign {
|
|
|
11
11
|
if (x === null || typeof x !== "object") return x;
|
|
12
12
|
if (Array.isArray(x)) return x.map(v => this.canonicalize(v));
|
|
13
13
|
const out = {};
|
|
14
|
+
|
|
14
15
|
for (const k of Object.keys(x).sort()) out[k] = this.canonicalize(x[k]);
|
|
15
16
|
return out;
|
|
16
17
|
}
|
|
@@ -19,48 +20,58 @@ export class dSyncSign {
|
|
|
19
20
|
return JSON.stringify(this.canonicalize(obj));
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
return
|
|
23
|
+
normalizePublicKey(key) {
|
|
24
|
+
return key
|
|
25
|
+
.replace(/\r|\n|\s+/g, '')
|
|
26
|
+
.replace('-----BEGINPUBLICKEY-----', '-----BEGIN PUBLIC KEY-----')
|
|
27
|
+
.replace('-----ENDPUBLICKEY-----', '-----END PUBLIC KEY-----')
|
|
28
|
+
.replace(/-----BEGIN PUBLIC KEY-----/, '-----BEGIN PUBLIC KEY-----\n')
|
|
29
|
+
.replace(/-----END PUBLIC KEY-----/, '\n-----END PUBLIC KEY-----')
|
|
30
|
+
.replace(/(.{64})/g, '$1\n')
|
|
31
|
+
.trim();
|
|
24
32
|
}
|
|
25
33
|
|
|
26
34
|
async ensureKeyPair() {
|
|
27
35
|
try {
|
|
28
36
|
const raw = await fs.readFile(this.KEY_FILE, "utf8");
|
|
29
|
-
const {
|
|
37
|
+
const {privateKey} = JSON.parse(raw);
|
|
38
|
+
|
|
30
39
|
crypto.createPrivateKey(privateKey);
|
|
31
|
-
|
|
32
|
-
|
|
40
|
+
|
|
41
|
+
const pubKey = crypto.createPublicKey(privateKey).export({type: "spki", format: "pem"});
|
|
42
|
+
return {privateKey, publicKey: pubKey.toString()};
|
|
33
43
|
} catch {
|
|
34
|
-
const {
|
|
44
|
+
const {privateKey, publicKey} = crypto.generateKeyPairSync("rsa", {
|
|
35
45
|
modulusLength: 2048,
|
|
36
|
-
publicKeyEncoding: {
|
|
37
|
-
privateKeyEncoding: {
|
|
46
|
+
publicKeyEncoding: {type: "spki", format: "pem"},
|
|
47
|
+
privateKeyEncoding: {type: "pkcs8", format: "pem"}
|
|
38
48
|
});
|
|
39
|
-
|
|
40
|
-
|
|
49
|
+
|
|
50
|
+
await fs.writeFile(this.KEY_FILE, JSON.stringify({privateKey}, null, 2), {encoding: "utf8", mode: 0o600});
|
|
51
|
+
return {privateKey, publicKey};
|
|
41
52
|
}
|
|
42
53
|
}
|
|
43
54
|
|
|
44
55
|
async getPrivateKey() {
|
|
45
|
-
const {
|
|
56
|
+
const {privateKey} = await this.ensureKeyPair();
|
|
46
57
|
return privateKey;
|
|
47
58
|
}
|
|
48
59
|
|
|
49
60
|
async getPublicKey() {
|
|
50
|
-
const {
|
|
61
|
+
const {publicKey} = await this.ensureKeyPair();
|
|
51
62
|
return publicKey;
|
|
52
63
|
}
|
|
53
64
|
|
|
54
65
|
async encrypt(data, recipient) {
|
|
55
66
|
const plaintext = typeof data === "string" ? data : this.stableStringify(data);
|
|
56
67
|
let aesKey;
|
|
57
|
-
let envelope = {
|
|
68
|
+
let envelope = {method: ""};
|
|
58
69
|
|
|
59
70
|
if (recipient.includes("BEGIN PUBLIC KEY") || recipient.includes("BEGIN RSA PUBLIC KEY")) {
|
|
60
71
|
aesKey = crypto.randomBytes(32);
|
|
61
72
|
envelope.method = "rsa";
|
|
62
73
|
envelope.encKey = crypto.publicEncrypt(
|
|
63
|
-
{
|
|
74
|
+
{key: recipient, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING},
|
|
64
75
|
aesKey
|
|
65
76
|
).toString("base64");
|
|
66
77
|
} else {
|
|
@@ -70,7 +81,9 @@ export class dSyncSign {
|
|
|
70
81
|
envelope.salt = salt.toString("base64");
|
|
71
82
|
}
|
|
72
83
|
|
|
73
|
-
|
|
84
|
+
// Standard-konformer 12-Byte-IV (statt 16)
|
|
85
|
+
const iv = crypto.randomBytes(12);
|
|
86
|
+
|
|
74
87
|
const cipher = crypto.createCipheriv("aes-256-gcm", aesKey, iv);
|
|
75
88
|
const ciphertext = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
76
89
|
const tag = cipher.getAuthTag();
|
|
@@ -88,7 +101,7 @@ export class dSyncSign {
|
|
|
88
101
|
if (envelope.method === "rsa") {
|
|
89
102
|
const priv = await this.getPrivateKey();
|
|
90
103
|
aesKey = crypto.privateDecrypt(
|
|
91
|
-
{
|
|
104
|
+
{key: priv, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING},
|
|
92
105
|
Buffer.from(envelope.encKey, "base64")
|
|
93
106
|
);
|
|
94
107
|
} else if (envelope.method === "password") {
|
|
@@ -124,16 +137,20 @@ export class dSyncSign {
|
|
|
124
137
|
const priv = await this.getPrivateKey();
|
|
125
138
|
const signer = crypto.createSign("SHA256");
|
|
126
139
|
const payload = typeof data === "string" ? data : this.stableStringify(data);
|
|
140
|
+
|
|
127
141
|
signer.update(payload, "utf8");
|
|
128
142
|
signer.end();
|
|
143
|
+
|
|
129
144
|
return signer.sign(priv, "base64");
|
|
130
145
|
}
|
|
131
146
|
|
|
132
147
|
verifyData(data, signature, publicKey) {
|
|
133
148
|
const verifier = crypto.createVerify("SHA256");
|
|
134
149
|
const payload = typeof data === "string" ? data : this.stableStringify(data);
|
|
150
|
+
|
|
135
151
|
verifier.update(payload, "utf8");
|
|
136
152
|
verifier.end();
|
|
153
|
+
|
|
137
154
|
return verifier.verify(publicKey, signature, "base64");
|
|
138
155
|
}
|
|
139
156
|
|
|
@@ -142,8 +159,10 @@ export class dSyncSign {
|
|
|
142
159
|
const re = /([^.\[\]]+)|\[(\d+)\]/g;
|
|
143
160
|
const parts = [];
|
|
144
161
|
let m;
|
|
162
|
+
|
|
145
163
|
while ((m = re.exec(path)) !== null) parts.push(m[1] !== undefined ? m[1] : Number(m[2]));
|
|
146
164
|
let cur = root;
|
|
165
|
+
|
|
147
166
|
for (const p of parts) {
|
|
148
167
|
if (cur == null) return undefined;
|
|
149
168
|
cur = cur[p];
|
|
@@ -154,6 +173,7 @@ export class dSyncSign {
|
|
|
154
173
|
cloneWithoutSig(obj) {
|
|
155
174
|
if (obj == null || typeof obj !== "object") return obj;
|
|
156
175
|
let copy;
|
|
176
|
+
|
|
157
177
|
if (typeof structuredClone === "function") {
|
|
158
178
|
try {
|
|
159
179
|
copy = structuredClone(obj);
|
|
@@ -163,16 +183,19 @@ export class dSyncSign {
|
|
|
163
183
|
} else {
|
|
164
184
|
copy = JSON.parse(JSON.stringify(obj));
|
|
165
185
|
}
|
|
186
|
+
|
|
166
187
|
if (copy && Object.prototype.hasOwnProperty.call(copy, this.sigField)) delete copy[this.sigField];
|
|
167
188
|
return copy;
|
|
168
189
|
}
|
|
169
190
|
|
|
170
191
|
async signJson(targetOrRoot, path) {
|
|
171
192
|
let target = path ? this.getByPath(targetOrRoot, path) : targetOrRoot;
|
|
193
|
+
|
|
172
194
|
if (target == null) {
|
|
173
195
|
if (path) return false;
|
|
174
196
|
throw new TypeError("target required");
|
|
175
197
|
}
|
|
198
|
+
|
|
176
199
|
if (Array.isArray(target)) {
|
|
177
200
|
const out = [];
|
|
178
201
|
for (const item of target) {
|
|
@@ -186,15 +209,18 @@ export class dSyncSign {
|
|
|
186
209
|
}
|
|
187
210
|
const payload = this.cloneWithoutSig(item);
|
|
188
211
|
const s = await this.signData(payload);
|
|
212
|
+
|
|
189
213
|
item[this.sigField] = s;
|
|
190
214
|
out.push(s);
|
|
191
215
|
}
|
|
192
216
|
return out;
|
|
193
217
|
}
|
|
218
|
+
|
|
194
219
|
if (typeof target === "object") {
|
|
195
220
|
if (Object.prototype.hasOwnProperty.call(target, this.sigField)) return target[this.sigField];
|
|
196
221
|
const payload = this.cloneWithoutSig(target);
|
|
197
222
|
const s = await this.signData(payload);
|
|
223
|
+
|
|
198
224
|
target[this.sigField] = s;
|
|
199
225
|
return s;
|
|
200
226
|
}
|
|
@@ -203,28 +229,35 @@ export class dSyncSign {
|
|
|
203
229
|
|
|
204
230
|
async verifyJson(targetOrRoot, publicKeyOrGetter, path) {
|
|
205
231
|
let target = path ? this.getByPath(targetOrRoot, path) : targetOrRoot;
|
|
232
|
+
|
|
206
233
|
if (target == null) {
|
|
207
234
|
if (path) return false;
|
|
208
235
|
throw new TypeError("target required");
|
|
209
236
|
}
|
|
237
|
+
|
|
210
238
|
if (Array.isArray(target)) {
|
|
211
239
|
const out = [];
|
|
240
|
+
|
|
212
241
|
for (const item of target) {
|
|
213
242
|
if (item == null || typeof item !== "object") {
|
|
214
243
|
out.push(false);
|
|
215
244
|
continue;
|
|
216
245
|
}
|
|
246
|
+
|
|
217
247
|
if (!Object.prototype.hasOwnProperty.call(item, this.sigField)) {
|
|
218
248
|
out.push(false);
|
|
219
249
|
continue;
|
|
220
250
|
}
|
|
251
|
+
|
|
221
252
|
const signature = item[this.sigField];
|
|
222
253
|
let pub = publicKeyOrGetter;
|
|
254
|
+
|
|
223
255
|
if (typeof publicKeyOrGetter === "function") pub = await publicKeyOrGetter(item, targetOrRoot);
|
|
224
256
|
if (!pub) {
|
|
225
257
|
out.push(false);
|
|
226
258
|
continue;
|
|
227
259
|
}
|
|
260
|
+
|
|
228
261
|
const payload = this.cloneWithoutSig(item);
|
|
229
262
|
out.push(Boolean(this.verifyData(payload, signature, pub)));
|
|
230
263
|
}
|
|
@@ -232,10 +265,13 @@ export class dSyncSign {
|
|
|
232
265
|
}
|
|
233
266
|
if (typeof target === "object") {
|
|
234
267
|
if (!Object.prototype.hasOwnProperty.call(target, this.sigField)) return false;
|
|
268
|
+
|
|
235
269
|
const signature = target[this.sigField];
|
|
236
270
|
let pub = publicKeyOrGetter;
|
|
271
|
+
|
|
237
272
|
if (typeof publicKeyOrGetter === "function") pub = await publicKeyOrGetter(target, targetOrRoot);
|
|
238
273
|
if (!pub) return false;
|
|
274
|
+
|
|
239
275
|
const payload = this.cloneWithoutSig(target);
|
|
240
276
|
return Boolean(this.verifyData(payload, signature, pub));
|
|
241
277
|
}
|