@1auth/crypto 0.0.0-alpha.43 → 0.0.0-alpha.45
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 +146 -100
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -18,28 +18,37 @@ const sign = promisify(signCallback)
|
|
|
18
18
|
const verify = promisify(verifyCallback)
|
|
19
19
|
|
|
20
20
|
const defaults = {
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
// symmetricEncryptionKey: randomBytes(32).toString('base64') // 256 bits
|
|
22
|
+
symmetricEncryptionKey: undefined,
|
|
23
|
+
symmetricEncryptionMethod: 'chacha20-poly1305', // 2024-05: AES-256 GCM (aes-256-gcm) or ChaCha20-Poly1305 (chacha20-poly1305)
|
|
24
|
+
symmetricEncryptionEncoding: 'base64', // https://nodejs.org/api/buffer.html#buffers-and-character-encodings
|
|
25
|
+
symmetricSignatureAlgorithm: 'sha3-384',
|
|
26
|
+
// symmetricSignatureSecret: randomBytes(32).toString('base64') // 256 bits
|
|
27
|
+
symmetricSignatureSecret: undefined,
|
|
28
|
+
asymmetricKeyNamedCurve: 'P-384', // P-512
|
|
29
|
+
asymmetricSignatureAlgorithm: 'sha3-384',
|
|
26
30
|
digestAlgorithm: 'sha3-384',
|
|
27
|
-
|
|
31
|
+
digestEncoding: 'hex',
|
|
28
32
|
signatureEncoding: 'base64'
|
|
29
33
|
}
|
|
30
|
-
const
|
|
34
|
+
const symmetricEncryptionEncodingLengths = {}
|
|
31
35
|
const options = {}
|
|
32
36
|
export default (opt = {}) => {
|
|
33
37
|
Object.assign(options, defaults, opt)
|
|
34
|
-
|
|
35
|
-
options.
|
|
38
|
+
symmetricEncryptionEncodingLengths.iv = randomBytes(12).toString(
|
|
39
|
+
options.symmetricEncryptionEncoding
|
|
36
40
|
).length
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
randomBytes(16).toString(options.
|
|
40
|
-
if (!options.
|
|
41
|
+
symmetricEncryptionEncodingLengths.ivAndAuthTag =
|
|
42
|
+
symmetricEncryptionEncodingLengths.iv +
|
|
43
|
+
randomBytes(16).toString(options.symmetricEncryptionEncoding).length
|
|
44
|
+
if (!options.symmetricEncryptionKey) {
|
|
41
45
|
console.warn(
|
|
42
|
-
"@1auth/crypto
|
|
46
|
+
"@1auth/crypto symmetricEncryptionKey is empty, use a stored secret made from randomBytes(32).toString('base64'). Encryption disabled."
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
if (!options.symmetricSignatureSecret) {
|
|
50
|
+
console.warn(
|
|
51
|
+
"@1auth/crypto symmetricSignatureSecret is empty, use a stored secret made from randomBytes(32).toString('base64'). Signature disabled."
|
|
43
52
|
)
|
|
44
53
|
}
|
|
45
54
|
}
|
|
@@ -126,10 +135,6 @@ export const randomNumeric = (characterLength) => {
|
|
|
126
135
|
return randomCharacters(characterLength, charactersNumeric)
|
|
127
136
|
}
|
|
128
137
|
|
|
129
|
-
export const randomSymetricEncryptionKey = () => {
|
|
130
|
-
return randomBytes(32).toString('base64') // 256 bits
|
|
131
|
-
}
|
|
132
|
-
|
|
133
138
|
// *** configs *** //
|
|
134
139
|
export const randomId = {
|
|
135
140
|
type: 'id',
|
|
@@ -140,9 +145,10 @@ export const randomId = {
|
|
|
140
145
|
}
|
|
141
146
|
|
|
142
147
|
// *** Digests *** //
|
|
143
|
-
export const createChecksum = (value, { algorithm } = {}) => {
|
|
148
|
+
export const createChecksum = (value, { algorithm, encoding } = {}) => {
|
|
144
149
|
algorithm ??= options.digestAlgorithm
|
|
145
|
-
|
|
150
|
+
encoding ??= options.digestEncoding
|
|
151
|
+
return checksum(algorithm).update(value).digest(encoding)
|
|
146
152
|
}
|
|
147
153
|
export const createDigest = (value, { algorithm } = {}) => {
|
|
148
154
|
algorithm ??= options.digestAlgorithm
|
|
@@ -153,19 +159,20 @@ export const createDigest = (value, { algorithm } = {}) => {
|
|
|
153
159
|
// TODO evaluate non-iv encryption approach?
|
|
154
160
|
// Use encryption as pepper to allow easier rotation of pepper
|
|
155
161
|
export const createEncryptedDigest = (value, { algorithm } = {}) => {
|
|
162
|
+
algorithm ??= options.digestAlgorithm
|
|
156
163
|
const digest = createDigest(value, { algorithm })
|
|
157
|
-
// encrypting using the
|
|
164
|
+
// encrypting using the symmetricEncryptionKey instead of
|
|
158
165
|
// the row encryptionKey to allow lookup, must have fixed iv
|
|
159
|
-
return
|
|
160
|
-
encryptionKey: options.
|
|
166
|
+
return symmetricEncrypt(digest, {
|
|
167
|
+
encryptionKey: options.symmetricEncryptionKey,
|
|
161
168
|
sub: '',
|
|
162
|
-
encoding: options.
|
|
169
|
+
encoding: options.symmetricEncryptionEncoding,
|
|
163
170
|
iv: Buffer.from(
|
|
164
|
-
options.
|
|
171
|
+
options.symmetricEncryptionKey.substring(
|
|
165
172
|
0,
|
|
166
|
-
|
|
173
|
+
symmetricEncryptionEncodingLengths.iv
|
|
167
174
|
),
|
|
168
|
-
options.
|
|
175
|
+
options.symmetricEncryptionEncoding
|
|
169
176
|
)
|
|
170
177
|
})
|
|
171
178
|
}
|
|
@@ -175,19 +182,19 @@ export const rotateDigestEncryption = (
|
|
|
175
182
|
encryptionKey,
|
|
176
183
|
newEncryptionKey
|
|
177
184
|
) => {
|
|
178
|
-
const digest =
|
|
185
|
+
const digest = symmetricDecrypt(encryptedValue, {
|
|
179
186
|
encryptionKey,
|
|
180
187
|
sub: '',
|
|
181
|
-
encoding: options.
|
|
188
|
+
encoding: options.symmetricEncryptionEncoding
|
|
182
189
|
})
|
|
183
190
|
|
|
184
|
-
return
|
|
191
|
+
return symmetricEncrypt(digest, {
|
|
185
192
|
encryptionKey: newEncryptionKey,
|
|
186
193
|
sub: '',
|
|
187
|
-
encoding: options.
|
|
194
|
+
encoding: options.symmetricEncryptionEncoding,
|
|
188
195
|
iv: Buffer.from(
|
|
189
|
-
newEncryptionKey.substring(0,
|
|
190
|
-
options.
|
|
196
|
+
newEncryptionKey.substring(0, symmetricEncryptionEncodingLengths.iv),
|
|
197
|
+
options.symmetricEncryptionEncoding
|
|
191
198
|
)
|
|
192
199
|
})
|
|
193
200
|
}
|
|
@@ -211,38 +218,42 @@ export const verifySecretHash = async (hash, value) => {
|
|
|
211
218
|
return secretVerify(hash, value)
|
|
212
219
|
}
|
|
213
220
|
|
|
214
|
-
// *** Encryption *** //
|
|
221
|
+
// *** Symmetric Encryption *** //
|
|
215
222
|
const authTagLength = 16
|
|
216
223
|
|
|
217
|
-
export const
|
|
218
|
-
|
|
224
|
+
export const symmetricRandomEncryptionKey = () => {
|
|
225
|
+
return randomBytes(32).toString('base64') // 256 bits
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export const symmetricGenerateEncryptionKey = (sub) => {
|
|
229
|
+
if (!options.symmetricEncryptionKey) {
|
|
219
230
|
return { encryptionKey: '', encryptedKey: '' }
|
|
220
231
|
}
|
|
221
|
-
const encryptionKey =
|
|
222
|
-
const encryptedKey =
|
|
223
|
-
encryptionKey: options.
|
|
232
|
+
const encryptionKey = symmetricRandomEncryptionKey()
|
|
233
|
+
const encryptedKey = symmetricEncrypt(encryptionKey, {
|
|
234
|
+
encryptionKey: options.symmetricEncryptionKey,
|
|
224
235
|
sub,
|
|
225
236
|
decoding: 'base64',
|
|
226
|
-
encoding: options.
|
|
237
|
+
encoding: options.symmetricEncryptionEncoding
|
|
227
238
|
})
|
|
228
239
|
return { encryptionKey, encryptedKey }
|
|
229
240
|
}
|
|
230
241
|
|
|
231
242
|
// sub add context to encryption
|
|
232
|
-
export const
|
|
243
|
+
export const symmetricEncryptFields = (
|
|
233
244
|
values,
|
|
234
245
|
{ encryptedKey, encryptionKey, sub },
|
|
235
246
|
fields = []
|
|
236
247
|
) => {
|
|
237
248
|
if (encryptedKey) {
|
|
238
|
-
encryptionKey ??=
|
|
249
|
+
encryptionKey ??= symmetricDecryptKey(encryptedKey, {
|
|
239
250
|
sub
|
|
240
251
|
})
|
|
241
252
|
}
|
|
242
253
|
if (!encryptionKey) return values
|
|
243
254
|
const encryptedValues = structuredClone(values)
|
|
244
255
|
for (const key of fields) {
|
|
245
|
-
encryptedValues[key] &&=
|
|
256
|
+
encryptedValues[key] &&= symmetricEncrypt(encryptedValues[key], {
|
|
246
257
|
encryptionKey,
|
|
247
258
|
sub
|
|
248
259
|
})
|
|
@@ -250,22 +261,22 @@ export const symetricEncryptFields = (
|
|
|
250
261
|
return encryptedValues
|
|
251
262
|
}
|
|
252
263
|
|
|
253
|
-
export const
|
|
264
|
+
export const symmetricEncrypt = (
|
|
254
265
|
data,
|
|
255
|
-
{ encryptedKey, encryptionKey, sub, decoding, encoding, iv }
|
|
266
|
+
{ encryptedKey, encryptionKey, signatureSecret, sub, decoding, encoding, iv }
|
|
256
267
|
) => {
|
|
257
268
|
if (encryptedKey) {
|
|
258
|
-
encryptionKey ??=
|
|
269
|
+
encryptionKey ??= symmetricDecryptKey(encryptedKey, {
|
|
259
270
|
sub
|
|
260
271
|
})
|
|
261
272
|
}
|
|
262
273
|
if (!encryptionKey || !data) return data
|
|
263
274
|
decoding ??= 'utf8'
|
|
264
|
-
encoding ??= options.
|
|
275
|
+
encoding ??= options.symmetricEncryptionEncoding
|
|
265
276
|
iv ??= randomBytes(12) // 96 bits
|
|
266
277
|
|
|
267
278
|
const cipher = createCipheriv(
|
|
268
|
-
options.
|
|
279
|
+
options.symmetricEncryptionMethod,
|
|
269
280
|
Buffer.from(encryptionKey, 'base64'),
|
|
270
281
|
iv,
|
|
271
282
|
{
|
|
@@ -280,23 +291,24 @@ export const symetricEncrypt = (
|
|
|
280
291
|
const encryptedDataPacket =
|
|
281
292
|
iv.toString(encoding) + authTag.toString(encoding) + encryptedData
|
|
282
293
|
|
|
283
|
-
|
|
294
|
+
// add signature to end
|
|
295
|
+
return symmetricSignatureSign(encryptedDataPacket, signatureSecret)
|
|
284
296
|
}
|
|
285
297
|
|
|
286
298
|
export const symmetricDecryptFields = (
|
|
287
299
|
encryptedValues,
|
|
288
|
-
{ encryptedKey, encryptionKey, sub },
|
|
300
|
+
{ encryptedKey, encryptionKey, signatureSecret, sub },
|
|
289
301
|
fields = []
|
|
290
302
|
) => {
|
|
291
303
|
if (encryptedKey) {
|
|
292
|
-
encryptionKey ??=
|
|
304
|
+
encryptionKey ??= symmetricDecryptKey(encryptedKey, {
|
|
293
305
|
sub
|
|
294
306
|
})
|
|
295
307
|
}
|
|
296
308
|
if (!encryptionKey) return encryptedValues
|
|
297
309
|
const values = structuredClone(encryptedValues)
|
|
298
310
|
for (const key of fields) {
|
|
299
|
-
values[key] &&=
|
|
311
|
+
values[key] &&= symmetricDecrypt(values[key], {
|
|
300
312
|
encryptionKey,
|
|
301
313
|
sub
|
|
302
314
|
})
|
|
@@ -304,48 +316,58 @@ export const symmetricDecryptFields = (
|
|
|
304
316
|
return values
|
|
305
317
|
}
|
|
306
318
|
|
|
307
|
-
export const
|
|
308
|
-
return
|
|
309
|
-
encryptionKey: options.
|
|
319
|
+
export const symmetricDecryptKey = (encryptedKey, { sub } = {}) => {
|
|
320
|
+
return symmetricDecrypt(encryptedKey, {
|
|
321
|
+
encryptionKey: options.symmetricEncryptionKey,
|
|
310
322
|
sub,
|
|
311
|
-
decoding: options.
|
|
323
|
+
decoding: options.symmetricEncryptionEncoding,
|
|
312
324
|
encoding: 'base64'
|
|
313
325
|
})
|
|
314
326
|
}
|
|
315
327
|
|
|
316
|
-
export const
|
|
328
|
+
export const symmetricDecrypt = (
|
|
317
329
|
encryptedDataPacket,
|
|
318
|
-
{ encryptedKey, encryptionKey, sub, decoding, encoding }
|
|
330
|
+
{ encryptedKey, encryptionKey, signatureSecret, sub, decoding, encoding }
|
|
319
331
|
) => {
|
|
320
332
|
if (encryptedKey) {
|
|
321
|
-
encryptionKey ??=
|
|
333
|
+
encryptionKey ??= symmetricDecryptKey(encryptedKey, {
|
|
322
334
|
sub
|
|
323
335
|
})
|
|
324
336
|
}
|
|
325
337
|
if (!encryptionKey || !encryptedDataPacket) return encryptedDataPacket
|
|
326
|
-
decoding ??= options.
|
|
338
|
+
decoding ??= options.symmetricEncryptionEncoding
|
|
327
339
|
encoding ??= 'utf8'
|
|
328
340
|
|
|
341
|
+
// remove signature when successful
|
|
342
|
+
encryptedDataPacket = symmetricSignatureVerify(
|
|
343
|
+
encryptedDataPacket,
|
|
344
|
+
signatureSecret
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
if (encryptedDataPacket === false) {
|
|
348
|
+
throw new Error('Signature incorrect')
|
|
349
|
+
}
|
|
350
|
+
|
|
329
351
|
const iv = Buffer.from(
|
|
330
|
-
encryptedDataPacket.substring(0,
|
|
352
|
+
encryptedDataPacket.substring(0, symmetricEncryptionEncodingLengths.iv),
|
|
331
353
|
decoding
|
|
332
354
|
)
|
|
333
355
|
const authTag = Buffer.from(
|
|
334
356
|
encryptedDataPacket.substring(
|
|
335
|
-
|
|
336
|
-
|
|
357
|
+
symmetricEncryptionEncodingLengths.iv,
|
|
358
|
+
symmetricEncryptionEncodingLengths.ivAndAuthTag
|
|
337
359
|
),
|
|
338
360
|
decoding
|
|
339
361
|
)
|
|
340
362
|
const encryptedData = Buffer.from(
|
|
341
363
|
encryptedDataPacket.substring(
|
|
342
|
-
|
|
364
|
+
symmetricEncryptionEncodingLengths.ivAndAuthTag
|
|
343
365
|
),
|
|
344
366
|
decoding
|
|
345
367
|
)
|
|
346
368
|
|
|
347
369
|
const decipher = createDecipheriv(
|
|
348
|
-
options.
|
|
370
|
+
options.symmetricEncryptionMethod,
|
|
349
371
|
Buffer.from(encryptionKey, 'base64'),
|
|
350
372
|
iv,
|
|
351
373
|
{
|
|
@@ -361,11 +383,65 @@ export const symetricDecrypt = (
|
|
|
361
383
|
return data
|
|
362
384
|
}
|
|
363
385
|
|
|
364
|
-
// *** Signatures *** //
|
|
386
|
+
// *** Symmetric Signatures *** //
|
|
387
|
+
export const symmetricRandomSignatureSecret = () => {
|
|
388
|
+
return randomBytes(32).toString('base64') // 256 bits
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export const symmetricGenerateSignatureSecret = (sub) => {
|
|
392
|
+
if (!options.symmetricSignatureSecret) {
|
|
393
|
+
return { signatureSecret: '' }
|
|
394
|
+
}
|
|
395
|
+
const signatureSecret = symmetricRandomSignatureSecret()
|
|
396
|
+
return { signatureSecret }
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
export const symmetricSignatureSign = (
|
|
400
|
+
data,
|
|
401
|
+
signatureSecret,
|
|
402
|
+
{ algorithm } = {}
|
|
403
|
+
) => {
|
|
404
|
+
// if (typeof data !== 'string') throw new TypeError('data must me a string')
|
|
405
|
+
signatureSecret ??= options.symmetricSignatureSecret
|
|
406
|
+
algorithm ??= options.symmetricSignatureAlgorithm
|
|
407
|
+
const signature = createHmac(algorithm, signatureSecret)
|
|
408
|
+
.update(data)
|
|
409
|
+
.digest(options.signatureEncoding)
|
|
410
|
+
.replace(/=+$/, '')
|
|
411
|
+
const signedData = data + '.' + signature
|
|
412
|
+
return signedData
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export const symmetricSignatureVerify = (
|
|
416
|
+
signedData,
|
|
417
|
+
signatureSecret,
|
|
418
|
+
{ algorithm } = {}
|
|
419
|
+
) => {
|
|
420
|
+
if (typeof signedData !== 'string') return false
|
|
421
|
+
const data = signedData.slice(0, signedData.lastIndexOf('.'))
|
|
422
|
+
const signedDataExpected = symmetricSignatureSign(data, signatureSecret, {
|
|
423
|
+
algorithm
|
|
424
|
+
})
|
|
425
|
+
|
|
426
|
+
return safeEqual(signedData, signedDataExpected) && data
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
export const symmetricRotation = (
|
|
430
|
+
encryptedValues,
|
|
431
|
+
oldOptions, // { encryptedKey, encryptionKey, signatureSecret, sub }, // old
|
|
432
|
+
newOptions, // { encryptedKey, encryptionKey, signatureSecret, sub, decoding, encoding, iv }, // new
|
|
433
|
+
fields = []
|
|
434
|
+
) => {
|
|
435
|
+
const data = symmetricDecryptFields(encryptedValues, oldOptions, fields)
|
|
436
|
+
const newEncryptedData = symmetricEncryptFields(data, newOptions, fields)
|
|
437
|
+
return newEncryptedData
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// *** Asymmetric Signatures *** //
|
|
365
441
|
// asymmetricKeyPairType
|
|
366
442
|
export const makeAsymmetricKeys = async () => {
|
|
367
443
|
const { publicKey, privateKey } = await generateKeyPair('ec', {
|
|
368
|
-
namedCurve:
|
|
444
|
+
namedCurve: options.asymmetricKeyNamedCurve,
|
|
369
445
|
paramEncoding: 'named',
|
|
370
446
|
publicKeyEncoding: {
|
|
371
447
|
type: 'spki',
|
|
@@ -375,7 +451,7 @@ export const makeAsymmetricKeys = async () => {
|
|
|
375
451
|
type: 'sec1',
|
|
376
452
|
format: 'pem'
|
|
377
453
|
// Encryption done at another level for consistency
|
|
378
|
-
// cipher: options.
|
|
454
|
+
// cipher: options.asymmetricEncryptionMethod,
|
|
379
455
|
// passphrase: encryptionKey,
|
|
380
456
|
}
|
|
381
457
|
})
|
|
@@ -387,7 +463,7 @@ export const makeAsymmetricSignature = async (
|
|
|
387
463
|
privateKey,
|
|
388
464
|
{ algorithm } = {}
|
|
389
465
|
) => {
|
|
390
|
-
algorithm ??= options.
|
|
466
|
+
algorithm ??= options.asymmetricSignatureAlgorithm
|
|
391
467
|
return (await sign(algorithm, Buffer.from(data), privateKey)).toString(
|
|
392
468
|
options.signatureEncoding
|
|
393
469
|
)
|
|
@@ -398,7 +474,7 @@ export const verifyAsymmetricSignature = async (
|
|
|
398
474
|
signature,
|
|
399
475
|
{ algorithm } = {}
|
|
400
476
|
) => {
|
|
401
|
-
algorithm ??= options.
|
|
477
|
+
algorithm ??= options.asymmetricSignatureAlgorithm
|
|
402
478
|
return await verify(
|
|
403
479
|
algorithm,
|
|
404
480
|
Buffer.from(data),
|
|
@@ -407,36 +483,6 @@ export const verifyAsymmetricSignature = async (
|
|
|
407
483
|
)
|
|
408
484
|
}
|
|
409
485
|
|
|
410
|
-
export const makeSymmetricSignature = (
|
|
411
|
-
data,
|
|
412
|
-
encryptionKey,
|
|
413
|
-
{ algorithm } = {}
|
|
414
|
-
) => {
|
|
415
|
-
// if (typeof data !== 'string') throw new TypeError('data must me a string')
|
|
416
|
-
encryptionKey ??= options.symetricEncryptionKey
|
|
417
|
-
algorithm ??= options.hmacAlgorithm
|
|
418
|
-
const signature = createHmac('sha256', encryptionKey)
|
|
419
|
-
.update(data)
|
|
420
|
-
.digest('base64')
|
|
421
|
-
.replace(/=+$/, '')
|
|
422
|
-
const signedData = data + '.' + signature
|
|
423
|
-
return signedData
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
export const verifySymmetricSignature = (
|
|
427
|
-
signedData,
|
|
428
|
-
encryptionKey,
|
|
429
|
-
{ algorithm } = {}
|
|
430
|
-
) => {
|
|
431
|
-
if (typeof signedData !== 'string') return false
|
|
432
|
-
const data = signedData.slice(0, signedData.lastIndexOf('.'))
|
|
433
|
-
const signedDataExpected = makeSymmetricSignature(data, encryptionKey, {
|
|
434
|
-
algorithm
|
|
435
|
-
})
|
|
436
|
-
|
|
437
|
-
return safeEqual(signedData, signedDataExpected) && data
|
|
438
|
-
}
|
|
439
|
-
|
|
440
486
|
export const safeEqual = (input, expected) => {
|
|
441
487
|
const bufferInput = Buffer.from(input)
|
|
442
488
|
const bufferExpected = Buffer.from(expected)
|