@1auth/crypto 0.0.0-alpha.57 → 0.0.0-alpha.59
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.js +260 -129
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { promisify } from 'node:util'
|
|
2
2
|
import {
|
|
3
3
|
randomBytes,
|
|
4
|
-
createHash
|
|
4
|
+
createHash,
|
|
5
5
|
createCipheriv,
|
|
6
6
|
createDecipheriv,
|
|
7
7
|
createHmac,
|
|
@@ -18,39 +18,63 @@ const sign = promisify(signCallback)
|
|
|
18
18
|
const verify = promisify(verifyCallback)
|
|
19
19
|
|
|
20
20
|
const defaults = {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
symmetricSignatureSecret: undefined,
|
|
21
|
+
symmetricEncryptionKey: undefined, // symmetricRandomEncryptionKey()
|
|
22
|
+
symmetricEncryptionAlgorithm: 'chacha20-poly1305', // 2024-05: AES-256 GCM (aes-256-gcm) or ChaCha20-Poly1305 (chacha20-poly1305)
|
|
23
|
+
symmetricEncryptionEncoding: undefined, // https://nodejs.org/api/buffer.html#buffers-and-character-encodings
|
|
24
|
+
symmetricSignatureHashAlgorithm: undefined, // fallback to defaultHashAlgorithm
|
|
25
|
+
symmetricSignatureSecret: undefined, // symmetricRandomSignatureSecret()
|
|
26
|
+
symmetricSignatureEncoding: undefined, // fallback to defaultEncoding
|
|
28
27
|
asymmetricKeyNamedCurve: 'P-384', // P-512
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
asymmetricSignatureHashAlgorithm: undefined, // fallback to defaultHashAlgorithm
|
|
29
|
+
asymmetricSignatureEncoding: undefined, // fallback to defaultEncoding
|
|
30
|
+
digestChecksumHashAlgorithm: undefined, // fallback to defaultHashAlgorithm
|
|
31
|
+
digestChecksumEncoding: undefined,
|
|
32
|
+
digestChecksumSalt: undefined, // randomChecksumSalt()
|
|
33
|
+
digestChecksumPepper: undefined, // randomChecksumPepper()
|
|
34
|
+
defaultEncoding: 'base64',
|
|
35
|
+
defaultHashAlgorithm: 'sha3-384'
|
|
33
36
|
}
|
|
34
37
|
const symmetricEncryptionEncodingLengths = {}
|
|
35
38
|
const options = {}
|
|
36
39
|
export default (opt = {}) => {
|
|
37
40
|
Object.assign(options, defaults, opt)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
).length
|
|
41
|
-
symmetricEncryptionEncodingLengths.ivAndAuthTag =
|
|
42
|
-
symmetricEncryptionEncodingLengths.iv +
|
|
43
|
-
randomBytes(16).toString(options.symmetricEncryptionEncoding).length
|
|
41
|
+
|
|
42
|
+
// Check options, set defaults
|
|
44
43
|
if (!options.symmetricEncryptionKey) {
|
|
45
44
|
console.warn(
|
|
46
|
-
|
|
45
|
+
'@1auth/crypto symmetricEncryptionKey is empty, use a stored secret made from randomBytes(32) Encryption disabled.'
|
|
47
46
|
)
|
|
48
47
|
}
|
|
48
|
+
options.symmetricEncryptionEncoding ??= options.defaultEncoding
|
|
49
|
+
options.symmetricSignatureHashAlgorithm ??= options.defaultHashAlgorithm
|
|
49
50
|
if (!options.symmetricSignatureSecret) {
|
|
50
51
|
console.warn(
|
|
51
|
-
|
|
52
|
+
'@1auth/crypto symmetricSignatureSecret is empty, use a stored secret made from randomBytes(32). Signature disabled.'
|
|
52
53
|
)
|
|
53
54
|
}
|
|
55
|
+
options.symmetricSignatureEncoding ??= options.defaultEncoding
|
|
56
|
+
options.asymmetricSignatureHashAlgorithm ??= options.defaultHashAlgorithm
|
|
57
|
+
options.asymmetricSignatureEncoding ??= options.defaultEncoding
|
|
58
|
+
if (!options.digestChecksumSalt) {
|
|
59
|
+
console.warn(
|
|
60
|
+
'@1auth/crypto digestChecksumSalt is empty, use a stored secret made from randomBytes(32). Checksum salting disabled.'
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
if (!options.digestChecksumPepper) {
|
|
64
|
+
console.warn(
|
|
65
|
+
'@1auth/crypto digestChecksumPepper is empty, use a stored secret made from randomBytes(12). Checksum peppering disabled.'
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
options.digestChecksumHashAlgorithm ??= options.defaultHashAlgorithm
|
|
69
|
+
options.digestChecksumEncoding ??= options.defaultEncoding
|
|
70
|
+
|
|
71
|
+
// Lengths
|
|
72
|
+
symmetricEncryptionEncodingLengths.iv = randomIV().toString(
|
|
73
|
+
options.symmetricEncryptionEncoding
|
|
74
|
+
).length
|
|
75
|
+
symmetricEncryptionEncodingLengths.ivAndAuthTag =
|
|
76
|
+
symmetricEncryptionEncodingLengths.iv +
|
|
77
|
+
randomBytes(16).toString(options.symmetricEncryptionEncoding).length
|
|
54
78
|
}
|
|
55
79
|
|
|
56
80
|
export const getOptions = () => options
|
|
@@ -86,13 +110,13 @@ export const entropyToCharacterLength = (bits, characterPoolSize) => {
|
|
|
86
110
|
}; */
|
|
87
111
|
|
|
88
112
|
// *** Random generators *** //
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
...'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
|
113
|
+
export const charactersAlpha = [
|
|
114
|
+
...'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
|
|
92
115
|
]
|
|
93
|
-
export const charactersDistinguishable = [...'CDEHKMPRTUWXY012458']
|
|
94
116
|
export const charactersNumeric = [...'0123456789']
|
|
117
|
+
export const charactersAlphaNumeric = charactersAlpha.concat(charactersNumeric)
|
|
95
118
|
|
|
119
|
+
// Ref: https://github.com/sindresorhus/crypto-random-string/blob/main/core.js
|
|
96
120
|
export const randomCharacters = (
|
|
97
121
|
length,
|
|
98
122
|
characters = charactersAlphaNumeric
|
|
@@ -145,58 +169,101 @@ export const randomId = {
|
|
|
145
169
|
}
|
|
146
170
|
|
|
147
171
|
// *** Digests *** //
|
|
148
|
-
export const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
export const createDigest = (value, { algorithm } = {}) => {
|
|
154
|
-
algorithm ??= options.digestAlgorithm
|
|
155
|
-
const checksum = createChecksum(value, { algorithm })
|
|
156
|
-
return `${algorithm}:${checksum}`
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// TODO evaluate non-iv encryption approach?
|
|
160
|
-
// Use encryption as pepper to allow easier rotation of pepper
|
|
161
|
-
export const createEncryptedDigest = (value, { algorithm } = {}) => {
|
|
162
|
-
algorithm ??= options.digestAlgorithm
|
|
163
|
-
const digest = createDigest(value, { algorithm })
|
|
164
|
-
// encrypting using the symmetricEncryptionKey instead of
|
|
165
|
-
// the row encryptionKey to allow lookup, must have fixed iv
|
|
166
|
-
return symmetricEncrypt(digest, {
|
|
167
|
-
encryptionKey: options.symmetricEncryptionKey,
|
|
168
|
-
sub: '',
|
|
169
|
-
encoding: options.symmetricEncryptionEncoding,
|
|
170
|
-
iv: Buffer.from(
|
|
171
|
-
options.symmetricEncryptionKey.substring(
|
|
172
|
-
0,
|
|
173
|
-
symmetricEncryptionEncodingLengths.iv
|
|
174
|
-
),
|
|
175
|
-
options.symmetricEncryptionEncoding
|
|
176
|
-
)
|
|
177
|
-
})
|
|
172
|
+
export const randomChecksumSalt = () => {
|
|
173
|
+
return randomBytes(32) // 256 bits
|
|
174
|
+
}
|
|
175
|
+
export const randomChecksumPepper = () => {
|
|
176
|
+
return randomIV() // 96
|
|
178
177
|
}
|
|
179
178
|
|
|
180
|
-
export const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
179
|
+
export const createSaltedValue = (value, { checksumSalt } = {}) => {
|
|
180
|
+
checksumSalt ??= options.digestChecksumSalt
|
|
181
|
+
if (!checksumSalt) {
|
|
182
|
+
return value
|
|
183
|
+
}
|
|
184
|
+
const newValue = value + checksumSalt
|
|
185
|
+
return newValue
|
|
186
|
+
}
|
|
187
|
+
export const createPepperedValue = (
|
|
188
|
+
value,
|
|
189
|
+
{ checksumPepper, encryptionKey } = {}
|
|
184
190
|
) => {
|
|
185
|
-
|
|
191
|
+
checksumPepper ??= options.digestChecksumPepper
|
|
192
|
+
encryptionKey ??= options.symmetricEncryptionKey
|
|
193
|
+
if (!checksumPepper || !encryptionKey) {
|
|
194
|
+
return value
|
|
195
|
+
}
|
|
196
|
+
const newValue = symmetricEncrypt(value, {
|
|
186
197
|
encryptionKey,
|
|
187
198
|
sub: '',
|
|
188
|
-
|
|
199
|
+
iv: checksumPepper
|
|
189
200
|
})
|
|
201
|
+
return newValue
|
|
202
|
+
}
|
|
190
203
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
204
|
+
export const createChecksum = (value, { hashAlgorithm, encoding } = {}) => {
|
|
205
|
+
hashAlgorithm ??= options.digestChecksumHashAlgorithm
|
|
206
|
+
encoding ??= options.digestChecksumEncoding
|
|
207
|
+
return createHash(hashAlgorithm).update(value).digest(encoding)
|
|
208
|
+
}
|
|
209
|
+
export const createSeasonedChecksum = (
|
|
210
|
+
value,
|
|
211
|
+
{ hashAlgorithm, encoding, checksumSalt, checksumPepper } = {}
|
|
212
|
+
) => {
|
|
213
|
+
return createChecksum(
|
|
214
|
+
createPepperedValue(createSaltedValue(value, { checksumSalt }), {
|
|
215
|
+
checksumPepper
|
|
216
|
+
}),
|
|
217
|
+
{
|
|
218
|
+
hashAlgorithm,
|
|
219
|
+
encoding
|
|
220
|
+
}
|
|
221
|
+
)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export const createDigest = (value, { hashAlgorithm, encoding } = {}) => {
|
|
225
|
+
hashAlgorithm ??= options.digestChecksumHashAlgorithm
|
|
226
|
+
const checksum = createChecksum(value, { hashAlgorithm, encoding })
|
|
227
|
+
return `${hashAlgorithm}:${checksum}`
|
|
228
|
+
}
|
|
229
|
+
export const createSaltedDigest = (
|
|
230
|
+
value,
|
|
231
|
+
{ hashAlgorithm, encoding, checksumSalt } = {}
|
|
232
|
+
) => {
|
|
233
|
+
hashAlgorithm ??= options.digestChecksumHashAlgorithm
|
|
234
|
+
const checksum = createChecksum(createSaltedValue(value, { checksumSalt }), {
|
|
235
|
+
hashAlgorithm,
|
|
236
|
+
encoding
|
|
237
|
+
})
|
|
238
|
+
return `${hashAlgorithm}:${checksum}`
|
|
239
|
+
}
|
|
240
|
+
export const createPepperedDigest = (
|
|
241
|
+
value,
|
|
242
|
+
{ hashAlgorithm, encoding, checksumPepper, encryptionKey } = {}
|
|
243
|
+
) => {
|
|
244
|
+
hashAlgorithm ??= options.digestChecksumHashAlgorithm
|
|
245
|
+
const checksum = createChecksum(
|
|
246
|
+
createPepperedValue(value, { checksumPepper, encryptionKey }),
|
|
247
|
+
{
|
|
248
|
+
hashAlgorithm,
|
|
249
|
+
encoding
|
|
250
|
+
}
|
|
251
|
+
)
|
|
252
|
+
return `${hashAlgorithm}:${checksum}`
|
|
253
|
+
}
|
|
254
|
+
export const createSeasonedDigest = (
|
|
255
|
+
value,
|
|
256
|
+
{ hashAlgorithm, encoding, checksumSalt, checksumPepper, encryptionKey } = {}
|
|
257
|
+
) => {
|
|
258
|
+
hashAlgorithm ??= options.digestChecksumHashAlgorithm
|
|
259
|
+
const checksum = createSeasonedChecksum(value, {
|
|
260
|
+
hashAlgorithm,
|
|
261
|
+
encoding,
|
|
262
|
+
checksumSalt,
|
|
263
|
+
checksumPepper,
|
|
264
|
+
encryptionKey
|
|
199
265
|
})
|
|
266
|
+
return `${hashAlgorithm}:${checksum}`
|
|
200
267
|
}
|
|
201
268
|
|
|
202
269
|
// *** Hashing *** //
|
|
@@ -222,31 +289,41 @@ export const verifySecretHash = async (hash, value) => {
|
|
|
222
289
|
const authTagLength = 16
|
|
223
290
|
|
|
224
291
|
export const symmetricRandomEncryptionKey = () => {
|
|
225
|
-
return randomBytes(32)
|
|
292
|
+
return randomBytes(32) // 256 bits
|
|
226
293
|
}
|
|
227
294
|
|
|
228
|
-
export const
|
|
229
|
-
|
|
295
|
+
export const randomIV = () => {
|
|
296
|
+
return randomBytes(12) // 96 bits
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export const symmetricGenerateEncryptionKey = (
|
|
300
|
+
sub,
|
|
301
|
+
{ encryptionKey, signatureSecret } = {}
|
|
302
|
+
) => {
|
|
303
|
+
encryptionKey ??= options.symmetricEncryptionKey
|
|
304
|
+
signatureSecret ??= options.symemeticSignatureSecret
|
|
305
|
+
|
|
306
|
+
if (!encryptionKey) {
|
|
230
307
|
return { encryptionKey: '', encryptedKey: '' }
|
|
231
308
|
}
|
|
232
|
-
const
|
|
233
|
-
const
|
|
234
|
-
encryptionKey
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
encoding: options.symmetricEncryptionEncoding
|
|
309
|
+
const rowEncryptionKey = symmetricRandomEncryptionKey()
|
|
310
|
+
const rowEncryptedKey = symmetricEncrypt(rowEncryptionKey, {
|
|
311
|
+
encryptionKey,
|
|
312
|
+
signatureSecret,
|
|
313
|
+
sub
|
|
238
314
|
})
|
|
239
|
-
return { encryptionKey, encryptedKey }
|
|
315
|
+
return { encryptionKey: rowEncryptionKey, encryptedKey: rowEncryptedKey }
|
|
240
316
|
}
|
|
241
317
|
|
|
242
318
|
// sub add context to encryption
|
|
243
319
|
export const symmetricEncryptFields = (
|
|
244
320
|
values,
|
|
245
|
-
{ encryptedKey, encryptionKey, sub },
|
|
321
|
+
{ encryptedKey, encryptionKey, signatureSecret, sub },
|
|
246
322
|
fields = []
|
|
247
323
|
) => {
|
|
248
324
|
if (encryptedKey) {
|
|
249
325
|
encryptionKey ??= symmetricDecryptKey(encryptedKey, {
|
|
326
|
+
signatureSecret,
|
|
250
327
|
sub
|
|
251
328
|
})
|
|
252
329
|
}
|
|
@@ -255,6 +332,7 @@ export const symmetricEncryptFields = (
|
|
|
255
332
|
for (const key of fields) {
|
|
256
333
|
encryptedValues[key] &&= symmetricEncrypt(encryptedValues[key], {
|
|
257
334
|
encryptionKey,
|
|
335
|
+
signatureSecret,
|
|
258
336
|
sub
|
|
259
337
|
})
|
|
260
338
|
}
|
|
@@ -267,23 +345,23 @@ export const symmetricEncrypt = (
|
|
|
267
345
|
) => {
|
|
268
346
|
if (encryptedKey) {
|
|
269
347
|
encryptionKey ??= symmetricDecryptKey(encryptedKey, {
|
|
348
|
+
signatureSecret,
|
|
270
349
|
sub
|
|
271
350
|
})
|
|
272
351
|
}
|
|
273
352
|
if (!encryptionKey || !data) return data
|
|
274
353
|
decoding ??= 'utf8'
|
|
275
354
|
encoding ??= options.symmetricEncryptionEncoding
|
|
276
|
-
iv ??=
|
|
277
|
-
|
|
355
|
+
iv ??= randomIV()
|
|
278
356
|
const cipher = createCipheriv(
|
|
279
|
-
options.
|
|
280
|
-
|
|
357
|
+
options.symmetricEncryptionAlgorithm,
|
|
358
|
+
encryptionKey,
|
|
281
359
|
iv,
|
|
282
360
|
{
|
|
283
361
|
authTagLength
|
|
284
362
|
}
|
|
285
363
|
)
|
|
286
|
-
cipher.setAAD(
|
|
364
|
+
cipher.setAAD(sub)
|
|
287
365
|
const encryptedData =
|
|
288
366
|
cipher.update(data, decoding, encoding) + cipher.final(encoding)
|
|
289
367
|
const authTag = cipher.getAuthTag()
|
|
@@ -292,7 +370,7 @@ export const symmetricEncrypt = (
|
|
|
292
370
|
iv.toString(encoding) + authTag.toString(encoding) + encryptedData
|
|
293
371
|
|
|
294
372
|
// add signature to end
|
|
295
|
-
return symmetricSignatureSign(encryptedDataPacket, signatureSecret)
|
|
373
|
+
return symmetricSignatureSign(encryptedDataPacket, { signatureSecret })
|
|
296
374
|
}
|
|
297
375
|
|
|
298
376
|
export const symmetricDecryptFields = (
|
|
@@ -302,6 +380,7 @@ export const symmetricDecryptFields = (
|
|
|
302
380
|
) => {
|
|
303
381
|
if (encryptedKey) {
|
|
304
382
|
encryptionKey ??= symmetricDecryptKey(encryptedKey, {
|
|
383
|
+
signatureSecret,
|
|
305
384
|
sub
|
|
306
385
|
})
|
|
307
386
|
}
|
|
@@ -310,19 +389,28 @@ export const symmetricDecryptFields = (
|
|
|
310
389
|
for (const key of fields) {
|
|
311
390
|
values[key] &&= symmetricDecrypt(values[key], {
|
|
312
391
|
encryptionKey,
|
|
392
|
+
signatureSecret,
|
|
313
393
|
sub
|
|
314
394
|
})
|
|
315
395
|
}
|
|
316
396
|
return values
|
|
317
397
|
}
|
|
318
398
|
|
|
319
|
-
export const symmetricDecryptKey = (
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
399
|
+
export const symmetricDecryptKey = (
|
|
400
|
+
encryptedKey,
|
|
401
|
+
{ sub, encryptionKey, signatureSecret } = {}
|
|
402
|
+
) => {
|
|
403
|
+
encryptionKey ??= options.symmetricEncryptionKey
|
|
404
|
+
signatureSecret ??= options.symemeticSignatureSecret
|
|
405
|
+
return Buffer.from(
|
|
406
|
+
symmetricDecrypt(encryptedKey, {
|
|
407
|
+
encryptionKey,
|
|
408
|
+
signatureSecret,
|
|
409
|
+
sub,
|
|
410
|
+
encoding: options.symmetricEncryptionEncoding
|
|
411
|
+
}),
|
|
412
|
+
options.symmetricEncryptionEncoding
|
|
413
|
+
)
|
|
326
414
|
}
|
|
327
415
|
|
|
328
416
|
export const symmetricDecrypt = (
|
|
@@ -331,6 +419,7 @@ export const symmetricDecrypt = (
|
|
|
331
419
|
) => {
|
|
332
420
|
if (encryptedKey) {
|
|
333
421
|
encryptionKey ??= symmetricDecryptKey(encryptedKey, {
|
|
422
|
+
signatureSecret,
|
|
334
423
|
sub
|
|
335
424
|
})
|
|
336
425
|
}
|
|
@@ -339,15 +428,13 @@ export const symmetricDecrypt = (
|
|
|
339
428
|
encoding ??= 'utf8'
|
|
340
429
|
|
|
341
430
|
// remove signature when successful
|
|
342
|
-
encryptedDataPacket = symmetricSignatureVerify(
|
|
343
|
-
encryptedDataPacket,
|
|
431
|
+
encryptedDataPacket = symmetricSignatureVerify(encryptedDataPacket, {
|
|
344
432
|
signatureSecret
|
|
345
|
-
)
|
|
433
|
+
})
|
|
346
434
|
|
|
347
435
|
if (encryptedDataPacket === false) {
|
|
348
436
|
throw new Error('Signature incorrect')
|
|
349
437
|
}
|
|
350
|
-
|
|
351
438
|
const iv = Buffer.from(
|
|
352
439
|
encryptedDataPacket.substring(0, symmetricEncryptionEncodingLengths.iv),
|
|
353
440
|
decoding
|
|
@@ -367,14 +454,14 @@ export const symmetricDecrypt = (
|
|
|
367
454
|
)
|
|
368
455
|
|
|
369
456
|
const decipher = createDecipheriv(
|
|
370
|
-
options.
|
|
371
|
-
|
|
457
|
+
options.symmetricEncryptionAlgorithm,
|
|
458
|
+
encryptionKey,
|
|
372
459
|
iv,
|
|
373
460
|
{
|
|
374
461
|
authTagLength
|
|
375
462
|
}
|
|
376
463
|
)
|
|
377
|
-
decipher.setAAD(
|
|
464
|
+
decipher.setAAD(sub)
|
|
378
465
|
|
|
379
466
|
decipher.setAuthTag(authTag)
|
|
380
467
|
const data =
|
|
@@ -385,10 +472,10 @@ export const symmetricDecrypt = (
|
|
|
385
472
|
|
|
386
473
|
// *** Symmetric Signatures *** //
|
|
387
474
|
export const symmetricRandomSignatureSecret = () => {
|
|
388
|
-
return randomBytes(32)
|
|
475
|
+
return randomBytes(32) // 256 bits
|
|
389
476
|
}
|
|
390
477
|
|
|
391
|
-
export const symmetricGenerateSignatureSecret = (
|
|
478
|
+
export const symmetricGenerateSignatureSecret = () => {
|
|
392
479
|
if (!options.symmetricSignatureSecret) {
|
|
393
480
|
return { signatureSecret: '' }
|
|
394
481
|
}
|
|
@@ -398,15 +485,13 @@ export const symmetricGenerateSignatureSecret = (sub) => {
|
|
|
398
485
|
|
|
399
486
|
export const symmetricSignatureSign = (
|
|
400
487
|
data,
|
|
401
|
-
signatureSecret
|
|
402
|
-
{ algorithm } = {}
|
|
488
|
+
{ hashAlgorithm, signatureSecret } = {}
|
|
403
489
|
) => {
|
|
404
|
-
// if (typeof data !== 'string') throw new TypeError('data must me a string')
|
|
405
490
|
signatureSecret ??= options.symmetricSignatureSecret
|
|
406
|
-
|
|
407
|
-
const signature = createHmac(
|
|
491
|
+
hashAlgorithm ??= options.symmetricSignatureHashAlgorithm
|
|
492
|
+
const signature = createHmac(hashAlgorithm, signatureSecret)
|
|
408
493
|
.update(data)
|
|
409
|
-
.digest(options.
|
|
494
|
+
.digest(options.symmetricSignatureEncoding)
|
|
410
495
|
.replace(/=+$/, '')
|
|
411
496
|
|
|
412
497
|
const signedData = data + '.' + signature
|
|
@@ -415,27 +500,73 @@ export const symmetricSignatureSign = (
|
|
|
415
500
|
|
|
416
501
|
export const symmetricSignatureVerify = (
|
|
417
502
|
signedData,
|
|
418
|
-
signatureSecret
|
|
419
|
-
{ algorithm } = {}
|
|
503
|
+
{ hashAlgorithm, signatureSecret } = {}
|
|
420
504
|
) => {
|
|
421
505
|
if (typeof signedData !== 'string') return false
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
506
|
+
let lastIndexOf = signedData.lastIndexOf('.')
|
|
507
|
+
// Test for unsigned
|
|
508
|
+
if (lastIndexOf < 0) {
|
|
509
|
+
lastIndexOf = signedData.length
|
|
510
|
+
}
|
|
511
|
+
const data = signedData.slice(0, lastIndexOf)
|
|
512
|
+
const signedDataExpected = symmetricSignatureSign(data, {
|
|
513
|
+
hashAlgorithm,
|
|
514
|
+
signatureSecret
|
|
425
515
|
})
|
|
426
|
-
|
|
427
516
|
return safeEqual(signedData, signedDataExpected) && data
|
|
428
517
|
}
|
|
429
518
|
|
|
519
|
+
// Allow rotation of global encryption key, global signature secret, and row encryption key
|
|
430
520
|
export const symmetricRotation = (
|
|
431
|
-
|
|
432
|
-
oldOptions, // {
|
|
433
|
-
|
|
434
|
-
|
|
521
|
+
oldEncryptedValues,
|
|
522
|
+
oldOptions, // { encryptionKey, signatureSecret, sub, decoding, encoding }, // old
|
|
523
|
+
oldFields = [],
|
|
524
|
+
newOptions, // { encryptionKey, signatureSecret, sub, decoding, encoding }, // new
|
|
525
|
+
newFields,
|
|
526
|
+
transform = (data) => {
|
|
527
|
+
return data
|
|
528
|
+
}
|
|
435
529
|
) => {
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
530
|
+
newOptions ??= oldOptions
|
|
531
|
+
newFields ??= oldFields
|
|
532
|
+
|
|
533
|
+
if (oldOptions.sub !== newOptions.sub) throw new Error('Mismatching `sub`')
|
|
534
|
+
|
|
535
|
+
oldEncryptedValues = structuredClone(oldEncryptedValues)
|
|
536
|
+
// Don't use structuredClone, converts Buffer to Uint8Array ...
|
|
537
|
+
oldOptions = { ...oldOptions }
|
|
538
|
+
newOptions = { ...newOptions }
|
|
539
|
+
|
|
540
|
+
// decrypt old encryption key
|
|
541
|
+
const { encryptionKey: oldEncryptedKey } = oldEncryptedValues
|
|
542
|
+
delete oldEncryptedValues.encryptionKey
|
|
543
|
+
|
|
544
|
+
const oldEncryptionKey = symmetricDecryptKey(oldEncryptedKey, oldOptions)
|
|
545
|
+
oldOptions.encryptionKey = oldEncryptionKey
|
|
546
|
+
|
|
547
|
+
// decrypt
|
|
548
|
+
const data = transform(
|
|
549
|
+
symmetricDecryptFields(
|
|
550
|
+
oldEncryptedValues,
|
|
551
|
+
{ ...oldOptions, encryptionKey: oldEncryptionKey },
|
|
552
|
+
oldFields
|
|
553
|
+
)
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
// rotate encryptionKey
|
|
557
|
+
const { encryptionKey: newEncryptionKey, encryptedKey: newEncryptedKey } =
|
|
558
|
+
symmetricGenerateEncryptionKey(newOptions.sub, newOptions)
|
|
559
|
+
newOptions.encryptionKey = newEncryptionKey
|
|
560
|
+
|
|
561
|
+
// encrypt
|
|
562
|
+
const newEncryptedValues = symmetricEncryptFields(
|
|
563
|
+
data,
|
|
564
|
+
newOptions,
|
|
565
|
+
newFields
|
|
566
|
+
)
|
|
567
|
+
newEncryptedValues.encryptionKey = newEncryptedKey
|
|
568
|
+
|
|
569
|
+
return newEncryptedValues
|
|
439
570
|
}
|
|
440
571
|
|
|
441
572
|
// *** Asymmetric Signatures *** //
|
|
@@ -452,7 +583,7 @@ export const makeAsymmetricKeys = async () => {
|
|
|
452
583
|
type: 'sec1',
|
|
453
584
|
format: 'pem'
|
|
454
585
|
// Encryption done at another level for consistency
|
|
455
|
-
// cipher: options.
|
|
586
|
+
// cipher: options.asymmetricEncryptionAlgorithm,
|
|
456
587
|
// passphrase: encryptionKey,
|
|
457
588
|
}
|
|
458
589
|
})
|
|
@@ -462,25 +593,25 @@ export const makeAsymmetricKeys = async () => {
|
|
|
462
593
|
export const makeAsymmetricSignature = async (
|
|
463
594
|
data,
|
|
464
595
|
privateKey,
|
|
465
|
-
{
|
|
596
|
+
{ hashAlgorithm } = {}
|
|
466
597
|
) => {
|
|
467
|
-
|
|
468
|
-
return (await sign(
|
|
469
|
-
options.
|
|
598
|
+
hashAlgorithm ??= options.asymmetricSignatureHashAlgorithm
|
|
599
|
+
return (await sign(hashAlgorithm, Buffer.from(data), privateKey)).toString(
|
|
600
|
+
options.asymmetricSignatureEncoding
|
|
470
601
|
)
|
|
471
602
|
}
|
|
472
603
|
export const verifyAsymmetricSignature = async (
|
|
473
604
|
data,
|
|
474
605
|
publicKey,
|
|
475
606
|
signature,
|
|
476
|
-
{
|
|
607
|
+
{ hashAlgorithm } = {}
|
|
477
608
|
) => {
|
|
478
|
-
|
|
609
|
+
hashAlgorithm ??= options.asymmetricSignatureHashAlgorithm
|
|
479
610
|
return await verify(
|
|
480
|
-
|
|
611
|
+
hashAlgorithm,
|
|
481
612
|
Buffer.from(data),
|
|
482
613
|
publicKey,
|
|
483
|
-
Buffer.from(signature, options.
|
|
614
|
+
Buffer.from(signature, options.asymmetricSignatureEncoding)
|
|
484
615
|
)
|
|
485
616
|
}
|
|
486
617
|
|