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