@1auth/crypto 0.0.0-alpha.60 → 0.0.0-alpha.62

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.
Files changed (2) hide show
  1. package/package.json +3 -2
  2. package/index.js +0 -625
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1auth/crypto",
3
- "version": "0.0.0-alpha.60",
3
+ "version": "0.0.0-alpha.62",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "engines": {
@@ -50,6 +50,7 @@
50
50
  "homepage": "https://github.com/willfarrell/1auth",
51
51
  "gitHead": "7a6c0fbb8ab71d6a2171e678697de9f237568431",
52
52
  "dependencies": {
53
- "@node-rs/argon2": "2.0.2"
53
+ "@node-rs/argon2": "2.0.2",
54
+ "nanoid": "5.1.5"
54
55
  }
55
56
  }
package/index.js DELETED
@@ -1,625 +0,0 @@
1
- import { promisify } from 'node:util'
2
- import {
3
- randomBytes,
4
- createHash,
5
- createCipheriv,
6
- createDecipheriv,
7
- createHmac,
8
- timingSafeEqual,
9
- generateKeyPair as generateKeyPairCallback,
10
- sign as signCallback,
11
- verify as verifyCallback
12
- } from 'node:crypto'
13
- // https://github.com/napi-rs/node-rs/tree/main/packages/argon2
14
- import { hash as secretHash, verify as secretVerify } from '@node-rs/argon2'
15
-
16
- const generateKeyPair = promisify(generateKeyPairCallback)
17
- const sign = promisify(signCallback)
18
- const verify = promisify(verifyCallback)
19
-
20
- const defaults = {
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
27
- asymmetricKeyNamedCurve: 'P-384', // P-512
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'
36
- }
37
- const symmetricEncryptionEncodingLengths = {}
38
- const options = {}
39
- export default (opt = {}) => {
40
- Object.assign(options, defaults, opt)
41
-
42
- // Check options, set defaults
43
- if (!options.symmetricEncryptionKey) {
44
- console.warn(
45
- '@1auth/crypto symmetricEncryptionKey is empty, use a stored secret made from randomBytes(32) Encryption disabled.'
46
- )
47
- }
48
- options.symmetricEncryptionEncoding ??= options.defaultEncoding
49
- options.symmetricSignatureHashAlgorithm ??= options.defaultHashAlgorithm
50
- if (!options.symmetricSignatureSecret) {
51
- console.warn(
52
- '@1auth/crypto symmetricSignatureSecret is empty, use a stored secret made from randomBytes(32). Signature disabled.'
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
78
- }
79
-
80
- export const getOptions = () => options
81
-
82
- // *** entropy *** //
83
- // export const characterPoolSize = (value) => {
84
- // const chars = value.split('')
85
- // let min = chars[0].charCodeAt()
86
- // let max = chars[0].charCodeAt()
87
- // for(const char of value.split('')) {
88
- // const code = char.charCodeAt()
89
- // if (code < min) {
90
- // min = code
91
- // } else if (max < code) {
92
- // max = code
93
- // }
94
- // }
95
- // return max - min
96
- // }
97
-
98
- // *** Helpers *** //
99
- // Ref: https://therootcompany.com/blog/how-many-bits-of-entropy-per-character/
100
- export const entropyToCharacterLength = (bits, characterPoolSize) => {
101
- // bits*ln(2)/ln(characterPoolSize)
102
- return Math.ceil((bits * Math.LN2) / Math.log(characterPoolSize))
103
- }
104
- /* export const characterLengthToEntropy = (
105
- characterLength,
106
- characterPoolSize,
107
- ) => {
108
- // log_2(characterPoolSize^characterLength)
109
- return Math.floor(Math.log2(characterPoolSize ** characterLength));
110
- }; */
111
-
112
- // *** Random generators *** //
113
- export const charactersAlpha = [
114
- ...'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
115
- ]
116
- export const charactersNumeric = [...'0123456789']
117
- export const charactersAlphaNumeric = charactersAlpha.concat(charactersNumeric)
118
-
119
- // Ref: https://github.com/sindresorhus/crypto-random-string/blob/main/core.js
120
- export const randomCharacters = (
121
- length,
122
- characters = charactersAlphaNumeric
123
- ) => {
124
- // Generating entropy is faster than complex math operations, so we use the simplest way
125
- const characterCount = characters.length
126
- const maxValidSelector =
127
- Math.floor(0x1_00_00 / characterCount) * characterCount - 1 // Using values above this will ruin distribution when using modular division
128
- const entropyLength = 2 * Math.ceil(1.1 * length) // Generating a bit more than required so chances we need more than one pass will be really low
129
- let string = ''
130
- let stringLength = 0
131
-
132
- while (stringLength < length) {
133
- // In case we had many bad values, which may happen for character sets of size above 0x8000 but close to it
134
- const entropy = new Uint8Array(randomBytes(entropyLength))
135
- let entropyPosition = 0
136
-
137
- while (entropyPosition < entropyLength && stringLength < length) {
138
- const entropyValue =
139
- entropy[entropyPosition] + (entropy[entropyPosition + 1] << 8) // eslint-disable-line no-bitwise
140
- entropyPosition += 2
141
- if (entropyValue > maxValidSelector) {
142
- // Skip values which will ruin distribution when using modular division
143
- continue
144
- }
145
-
146
- string += characters[entropyValue % characterCount]
147
- stringLength++
148
- }
149
- }
150
-
151
- return string
152
- }
153
-
154
- export const randomAlphaNumeric = (characterLength) => {
155
- return randomCharacters(characterLength, charactersAlphaNumeric)
156
- }
157
-
158
- export const randomNumeric = (characterLength) => {
159
- return randomCharacters(characterLength, charactersNumeric)
160
- }
161
-
162
- // *** configs *** //
163
- export const randomId = {
164
- type: 'id',
165
- minLength: entropyToCharacterLength(64, charactersAlphaNumeric.length),
166
- // TODO update to use https://github.com/jetpack-io/typeid
167
- create: (prefix) =>
168
- (prefix ? prefix + '_' : '') + randomAlphaNumeric(randomId.minLength)
169
- }
170
-
171
- // *** Digests *** //
172
- export const randomChecksumSalt = () => {
173
- return randomBytes(32) // 256 bits
174
- }
175
- export const randomChecksumPepper = () => {
176
- return randomIV() // 96
177
- }
178
-
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 } = {}
190
- ) => {
191
- checksumPepper ??= options.digestChecksumPepper
192
- encryptionKey ??= options.symmetricEncryptionKey
193
- if (!checksumPepper || !encryptionKey) {
194
- return value
195
- }
196
- const newValue = symmetricEncrypt(value, {
197
- encryptionKey,
198
- sub: '',
199
- iv: checksumPepper
200
- })
201
- return newValue
202
- }
203
-
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
265
- })
266
- return `${hashAlgorithm}:${checksum}`
267
- }
268
-
269
- // *** Hashing *** //
270
- const hashOptions = {
271
- timeCost: 3,
272
- memoryCost: 2 ** 16,
273
- saltLength: 16,
274
- parallelism: 1,
275
- outputLen: 64, // hashLength: 128
276
- algorithm: 2,
277
- version: 1
278
- }
279
-
280
- export const createSecretHash = async (value, options = hashOptions) => {
281
- return secretHash(value, options)
282
- }
283
-
284
- export const verifySecretHash = async (hash, value) => {
285
- return secretVerify(hash, value)
286
- }
287
-
288
- // *** Symmetric Encryption *** //
289
- const authTagLength = 16
290
-
291
- export const symmetricRandomEncryptionKey = () => {
292
- return randomBytes(32) // 256 bits
293
- }
294
-
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) {
307
- return { encryptionKey: '', encryptedKey: '' }
308
- }
309
- const rowEncryptionKey = symmetricRandomEncryptionKey()
310
- const rowEncryptedKey = symmetricEncrypt(rowEncryptionKey, {
311
- encryptionKey,
312
- signatureSecret,
313
- sub
314
- })
315
- return { encryptionKey: rowEncryptionKey, encryptedKey: rowEncryptedKey }
316
- }
317
-
318
- // sub add context to encryption
319
- export const symmetricEncryptFields = (
320
- values,
321
- { encryptedKey, encryptionKey, signatureSecret, sub },
322
- fields = []
323
- ) => {
324
- if (encryptedKey) {
325
- encryptionKey ??= symmetricDecryptKey(encryptedKey, {
326
- signatureSecret,
327
- sub
328
- })
329
- }
330
- if (!encryptionKey) return values
331
- const encryptedValues = structuredClone(values)
332
- for (const key of fields) {
333
- encryptedValues[key] &&= symmetricEncrypt(encryptedValues[key], {
334
- encryptionKey,
335
- signatureSecret,
336
- sub
337
- })
338
- }
339
- return encryptedValues
340
- }
341
-
342
- export const symmetricEncrypt = (
343
- data,
344
- { encryptedKey, encryptionKey, signatureSecret, sub, decoding, encoding, iv }
345
- ) => {
346
- if (encryptedKey) {
347
- encryptionKey ??= symmetricDecryptKey(encryptedKey, {
348
- signatureSecret,
349
- sub
350
- })
351
- }
352
- if (!encryptionKey || !data) return data
353
- decoding ??= 'utf8'
354
- encoding ??= options.symmetricEncryptionEncoding
355
- iv ??= randomIV()
356
- const cipher = createCipheriv(
357
- options.symmetricEncryptionAlgorithm,
358
- encryptionKey,
359
- iv,
360
- {
361
- authTagLength
362
- }
363
- )
364
- cipher.setAAD(sub)
365
- const encryptedData =
366
- cipher.update(data, decoding, encoding) + cipher.final(encoding)
367
- const authTag = cipher.getAuthTag()
368
-
369
- const encryptedDataPacket =
370
- iv.toString(encoding) + authTag.toString(encoding) + encryptedData
371
-
372
- // add signature to end
373
- return symmetricSignatureSign(encryptedDataPacket, { signatureSecret })
374
- }
375
-
376
- export const symmetricDecryptFields = (
377
- encryptedValues,
378
- { encryptedKey, encryptionKey, signatureSecret, sub },
379
- fields = []
380
- ) => {
381
- if (encryptedKey) {
382
- encryptionKey ??= symmetricDecryptKey(encryptedKey, {
383
- signatureSecret,
384
- sub
385
- })
386
- }
387
- if (!encryptionKey) return encryptedValues
388
- const values = structuredClone(encryptedValues)
389
- for (const key of fields) {
390
- values[key] &&= symmetricDecrypt(values[key], {
391
- encryptionKey,
392
- signatureSecret,
393
- sub
394
- })
395
- }
396
- return values
397
- }
398
-
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
- )
414
- }
415
-
416
- export const symmetricDecrypt = (
417
- encryptedDataPacket,
418
- { encryptedKey, encryptionKey, signatureSecret, sub, decoding, encoding }
419
- ) => {
420
- if (encryptedKey) {
421
- encryptionKey ??= symmetricDecryptKey(encryptedKey, {
422
- signatureSecret,
423
- sub
424
- })
425
- }
426
- if (!encryptionKey || !encryptedDataPacket) return encryptedDataPacket
427
- decoding ??= options.symmetricEncryptionEncoding
428
- encoding ??= 'utf8'
429
-
430
- // remove signature when successful
431
- encryptedDataPacket = symmetricSignatureVerify(encryptedDataPacket, {
432
- signatureSecret
433
- })
434
-
435
- if (encryptedDataPacket === false) {
436
- throw new Error('Signature incorrect')
437
- }
438
- const iv = Buffer.from(
439
- encryptedDataPacket.substring(0, symmetricEncryptionEncodingLengths.iv),
440
- decoding
441
- )
442
- const authTag = Buffer.from(
443
- encryptedDataPacket.substring(
444
- symmetricEncryptionEncodingLengths.iv,
445
- symmetricEncryptionEncodingLengths.ivAndAuthTag
446
- ),
447
- decoding
448
- )
449
- const encryptedData = Buffer.from(
450
- encryptedDataPacket.substring(
451
- symmetricEncryptionEncodingLengths.ivAndAuthTag
452
- ),
453
- decoding
454
- )
455
-
456
- const decipher = createDecipheriv(
457
- options.symmetricEncryptionAlgorithm,
458
- encryptionKey,
459
- iv,
460
- {
461
- authTagLength
462
- }
463
- )
464
- decipher.setAAD(sub)
465
-
466
- decipher.setAuthTag(authTag)
467
- const data =
468
- decipher.update(encryptedData, decoding, encoding) +
469
- decipher.final(encoding)
470
- return data
471
- }
472
-
473
- // *** Symmetric Signatures *** //
474
- export const symmetricRandomSignatureSecret = () => {
475
- return randomBytes(32) // 256 bits
476
- }
477
-
478
- export const symmetricGenerateSignatureSecret = () => {
479
- if (!options.symmetricSignatureSecret) {
480
- return { signatureSecret: '' }
481
- }
482
- const signatureSecret = symmetricRandomSignatureSecret()
483
- return { signatureSecret }
484
- }
485
-
486
- export const symmetricSignatureSign = (
487
- data,
488
- { hashAlgorithm, signatureSecret } = {}
489
- ) => {
490
- signatureSecret ??= options.symmetricSignatureSecret
491
- hashAlgorithm ??= options.symmetricSignatureHashAlgorithm
492
- const signature = createHmac(hashAlgorithm, signatureSecret)
493
- .update(data)
494
- .digest(options.symmetricSignatureEncoding)
495
- .replace(/=+$/, '')
496
-
497
- const signedData = data + '.' + signature
498
- return signedData
499
- }
500
-
501
- export const symmetricSignatureVerify = (
502
- signedData,
503
- { hashAlgorithm, signatureSecret } = {}
504
- ) => {
505
- if (typeof signedData !== 'string') return false
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
515
- })
516
- return safeEqual(signedData, signedDataExpected) && data
517
- }
518
-
519
- // Allow rotation of global encryption key, global signature secret, and row encryption key
520
- export const symmetricRotation = (
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
- }
529
- ) => {
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
570
- }
571
-
572
- // *** Asymmetric Signatures *** //
573
- // asymmetricKeyPairType
574
- export const makeAsymmetricKeys = async () => {
575
- const { publicKey, privateKey } = await generateKeyPair('ec', {
576
- namedCurve: options.asymmetricKeyNamedCurve,
577
- paramEncoding: 'named',
578
- publicKeyEncoding: {
579
- type: 'spki',
580
- format: 'pem'
581
- },
582
- privateKeyEncoding: {
583
- type: 'sec1',
584
- format: 'pem'
585
- // Encryption done at another level for consistency
586
- // cipher: options.asymmetricEncryptionAlgorithm,
587
- // passphrase: encryptionKey,
588
- }
589
- })
590
- return { publicKey, privateKey }
591
- }
592
-
593
- export const makeAsymmetricSignature = async (
594
- data,
595
- privateKey,
596
- { hashAlgorithm } = {}
597
- ) => {
598
- hashAlgorithm ??= options.asymmetricSignatureHashAlgorithm
599
- return (await sign(hashAlgorithm, Buffer.from(data), privateKey)).toString(
600
- options.asymmetricSignatureEncoding
601
- )
602
- }
603
- export const verifyAsymmetricSignature = async (
604
- data,
605
- publicKey,
606
- signature,
607
- { hashAlgorithm } = {}
608
- ) => {
609
- hashAlgorithm ??= options.asymmetricSignatureHashAlgorithm
610
- return await verify(
611
- hashAlgorithm,
612
- Buffer.from(data),
613
- publicKey,
614
- Buffer.from(signature, options.asymmetricSignatureEncoding)
615
- )
616
- }
617
-
618
- export const safeEqual = (input, expected) => {
619
- const bufferInput = Buffer.from(input)
620
- const bufferExpected = Buffer.from(expected)
621
- return (
622
- bufferInput.length === bufferExpected.length &&
623
- timingSafeEqual(bufferInput, bufferExpected)
624
- )
625
- }