@1auth/crypto 0.0.0-alpha.5 → 0.0.0-alpha.50

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 (3) hide show
  1. package/index.js +368 -274
  2. package/package.json +5 -5
  3. package/LICENSE +0 -21
package/index.js CHANGED
@@ -4,27 +4,56 @@ import {
4
4
  createHash as checksum,
5
5
  createCipheriv,
6
6
  createDecipheriv,
7
+ createHmac,
8
+ timingSafeEqual,
7
9
  generateKeyPair as generateKeyPairCallback,
8
- sign,
9
- verify
10
+ sign as signCallback,
11
+ verify as verifyCallback
10
12
  } from 'node:crypto'
11
13
  // https://github.com/napi-rs/node-rs/tree/main/packages/argon2
12
14
  import { hash as secretHash, verify as secretVerify } from '@node-rs/argon2'
13
15
 
14
- const options = {
15
- // randomBytes(32).toString('hex') // 256 bits
16
- encryptionSharedKey: '',
17
- encryptionMethod: 'chacha20-poly1305', // AES-256 GCM (aes-256-gcm) or ChaCha20-Poly1305 (chacha20-poly1305)
18
-
19
- digestAlgorithm: 'sha3-256',
20
- digestSalt: ''
16
+ const generateKeyPair = promisify(generateKeyPairCallback)
17
+ const sign = promisify(signCallback)
18
+ const verify = promisify(verifyCallback)
19
+
20
+ const defaults = {
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',
30
+ digestAlgorithm: 'sha3-384',
31
+ digestEncoding: 'hex',
32
+ signatureEncoding: 'base64'
21
33
  }
22
-
23
- export default (params) => {
24
- Object.assign(options, params)
34
+ const symmetricEncryptionEncodingLengths = {}
35
+ const options = {}
36
+ export default (opt = {}) => {
37
+ Object.assign(options, defaults, opt)
38
+ symmetricEncryptionEncodingLengths.iv = randomBytes(12).toString(
39
+ options.symmetricEncryptionEncoding
40
+ ).length
41
+ symmetricEncryptionEncodingLengths.ivAndAuthTag =
42
+ symmetricEncryptionEncodingLengths.iv +
43
+ randomBytes(16).toString(options.symmetricEncryptionEncoding).length
44
+ if (!options.symmetricEncryptionKey) {
45
+ console.warn(
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."
52
+ )
53
+ }
25
54
  }
26
55
 
27
- const generateKeyPair = promisify(generateKeyPairCallback)
56
+ export const getOptions = () => options
28
57
 
29
58
  // *** entropy *** //
30
59
  // export const characterPoolSize = (value) => {
@@ -42,184 +71,132 @@ const generateKeyPair = promisify(generateKeyPairCallback)
42
71
  // return max - min
43
72
  // }
44
73
 
45
- // *** Random generators *** //
46
- export const characterPoolSize = {
47
- keyboard: 94, // (26 + 10 + 11) * 2
48
- alphaNumeric: 62, // (26 + 10) * 2
49
- base64: 64, // (26 + 10) * 2 + 2
50
- hex: 16,
51
- numeric: 10
52
- }
53
- // Alt: https://github.com/sindresorhus/crypto-random-string/blob/main/core.js
54
- export const randomAlphaNumeric = (bits) => {
55
- const characterLength = entropyToCharacterLength(
56
- bits,
57
- characterPoolSize.alphaNumeric
58
- )
59
- return randomBytes(characterLength * 2)
60
- .toString('base64')
61
- .replace(/[^a-zA-Z0-9]/g, '')
62
- .substring(0, characterLength)
63
- }
64
-
65
- export const randomBase64 = (bits) => {
66
- const characterLength = entropyToCharacterLength(
67
- bits,
68
- characterPoolSize.base64
69
- )
70
- return randomBytes(characterLength + 1)
71
- .toString('base64')
72
- .replace('=', '')
74
+ // *** Helpers *** //
75
+ // Ref: https://therootcompany.com/blog/how-many-bits-of-entropy-per-character/
76
+ export const entropyToCharacterLength = (bits, characterPoolSize) => {
77
+ // bits*ln(2)/ln(characterPoolSize)
78
+ return Math.ceil((bits * Math.LN2) / Math.log(characterPoolSize))
73
79
  }
80
+ /* export const characterLengthToEntropy = (
81
+ characterLength,
82
+ characterPoolSize,
83
+ ) => {
84
+ // log_2(characterPoolSize^characterLength)
85
+ return Math.floor(Math.log2(characterPoolSize ** characterLength));
86
+ }; */
74
87
 
75
- export const randomNumeric = (bits) => {
76
- const characterLength = entropyToCharacterLength(
77
- bits,
78
- characterPoolSize.numeric
79
- )
80
- const max = 10 ** characterLength
81
- let value = Math.floor(Math.random() * max).toString()
82
- if (value === max.toString()) {
83
- return randomNumeric(bits)
88
+ // *** Random generators *** //
89
+ // Ref: https://github.com/sindresorhus/crypto-random-string/blob/main/core.js
90
+ export const charactersAlphaNumeric = [
91
+ ...'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
92
+ ]
93
+ export const charactersDistinguishable = [...'CDEHKMPRTUWXY012458']
94
+ export const charactersNumeric = [...'0123456789']
95
+
96
+ export const randomCharacters = (
97
+ length,
98
+ characters = charactersAlphaNumeric
99
+ ) => {
100
+ // Generating entropy is faster than complex math operations, so we use the simplest way
101
+ const characterCount = characters.length
102
+ const maxValidSelector =
103
+ Math.floor(0x1_00_00 / characterCount) * characterCount - 1 // Using values above this will ruin distribution when using modular division
104
+ 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
105
+ let string = ''
106
+ let stringLength = 0
107
+
108
+ while (stringLength < length) {
109
+ // In case we had many bad values, which may happen for character sets of size above 0x8000 but close to it
110
+ const entropy = new Uint8Array(randomBytes(entropyLength))
111
+ let entropyPosition = 0
112
+
113
+ while (entropyPosition < entropyLength && stringLength < length) {
114
+ const entropyValue =
115
+ entropy[entropyPosition] + (entropy[entropyPosition + 1] << 8) // eslint-disable-line no-bitwise
116
+ entropyPosition += 2
117
+ if (entropyValue > maxValidSelector) {
118
+ // Skip values which will ruin distribution when using modular division
119
+ continue
120
+ }
121
+
122
+ string += characters[entropyValue % characterCount]
123
+ stringLength++
124
+ }
84
125
  }
85
- while (value.length < characterLength) value = '0' + value
86
- return value
87
- }
88
126
 
89
- export const randomHex = (bits) => {
90
- const characterLength = entropyToCharacterLength(bits, characterPoolSize.hex)
91
- return randomBytes(characterLength).toString('hex')
127
+ return string
92
128
  }
93
129
 
94
- // *** configs *** //
95
- export const randomId = {
96
- type: 'id',
97
- entropy: 64,
98
- charPool: characterPoolSize.alphaNumeric,
99
- create: async () => randomAlphaNumeric(randomId.entropy)
130
+ export const randomAlphaNumeric = (characterLength) => {
131
+ return randomCharacters(characterLength, charactersAlphaNumeric)
100
132
  }
101
133
 
102
- export const subject = {
103
- type: 'id',
104
- entropy: 64,
105
- charPool: characterPoolSize.alphaNumeric,
106
- create: async () => randomAlphaNumeric(subject.entropy)
134
+ export const randomNumeric = (characterLength) => {
135
+ return randomCharacters(characterLength, charactersNumeric)
107
136
  }
108
137
 
109
- export const session = {
138
+ // *** configs *** //
139
+ export const randomId = {
110
140
  type: 'id',
111
- entropy: 128, // ASVS 3.2.2
112
- charPool: characterPoolSize.alphaNumeric,
113
- expire: 15 * 60,
114
- create: async () => randomAlphaNumeric(session.entropy)
115
- }
116
-
117
- export const passwordSecret = {
118
- type: 'secret',
119
- entropy: 64,
120
- charPool: characterPoolSize.keyboard,
121
- otp: false,
122
- encode: async (value, encryptedKey, sub) =>
123
- createSecretHash(value).then((hash) => encrypt(hash, encryptedKey, sub)),
124
- decode: async (value, encryptedKey, sub) => decrypt(value, encryptedKey, sub),
125
- verify: async (value, hash) => verifySecretHash(hash, value)
126
- }
127
-
128
- // aka Password/Credential Recovery
129
- export const passwordToken = {
130
- type: 'token',
131
- entropy: 20,
132
- charPool: characterPoolSize.numeric,
133
- otp: true,
134
- expire: 30,
135
- create: async () => randomNumeric(passwordToken.entropy),
136
- encode: async (value, encryptedKey, sub) =>
137
- createSecretHash(value).then((hash) => encrypt(hash, encryptedKey, sub)),
138
- decode: async (value, encryptedKey, sub) => decrypt(value, encryptedKey, sub),
139
- verify: async (value, hash) => verifySecretHash(hash, value)
140
- }
141
-
142
- export const oneTimeSecret = {
143
- type: 'secret',
144
- entropy: 64, //
145
- charPool: characterPoolSize.base64,
146
- otp: false,
147
- expire: null,
148
- create: async () => randomBase64(oneTimeSecret.entropy),
149
- encode: async (value, encryptedKey, sub) => encrypt(value, encryptedKey, sub),
150
- decode: async (value, encryptedKey, sub) => decrypt(value, encryptedKey, sub)
151
- // create, hash, verify to be handled within
152
- }
153
- export const oneTimeToken = {
154
- type: 'token',
155
- entropy: 20,
156
- charPool: characterPoolSize.numeric,
157
- otp: true,
158
- expire: 30
159
- // create, hash, verify to be handled within
141
+ minLength: entropyToCharacterLength(64, charactersAlphaNumeric.length),
142
+ // TODO update to use https://github.com/jetpack-io/typeid
143
+ create: (prefix) =>
144
+ (prefix ? prefix + '_' : '') + randomAlphaNumeric(randomId.minLength)
160
145
  }
161
146
 
162
- // aka lookup secret
163
- export const recoveryCode = {
164
- type: 'secret',
165
- entropy: 112,
166
- charPool: characterPoolSize.alphaNumeric,
167
- otp: true,
168
- create: async () => randomAlphaNumeric(recoveryCode.entropy),
169
- encode: async (value, encryptedKey, sub) =>
170
- createSecretHash(value).then((hash) => encrypt(hash, encryptedKey, sub)),
171
- decode: async (value, encryptedKey, sub) => decrypt(value, encryptedKey, sub),
172
- verify: async (value, hash) => verifySecretHash(hash, value)
147
+ // *** Digests *** //
148
+ export const createChecksum = (value, { algorithm, encoding } = {}) => {
149
+ algorithm ??= options.digestAlgorithm
150
+ encoding ??= options.digestEncoding
151
+ return checksum(algorithm).update(value).digest(encoding)
173
152
  }
174
-
175
- export const outOfBandToken = {
176
- type: 'token',
177
- entropy: 20,
178
- charPool: characterPoolSize.numeric,
179
- otp: true,
180
- expire: 10 * 60,
181
- create: async () => randomNumeric(outOfBandToken.entropy),
182
- encode: async (value, encryptedKey, sub) =>
183
- createSecretHash(value).then((hash) => encrypt(hash, encryptedKey, sub)),
184
- decode: async (value, encryptedKey, sub) => decrypt(value, encryptedKey, sub),
185
- verify: async (value, hash) => verifySecretHash(hash, value)
153
+ export const createDigest = (value, { algorithm } = {}) => {
154
+ algorithm ??= options.digestAlgorithm
155
+ const checksum = createChecksum(value, { algorithm })
156
+ return `${algorithm}:${checksum}`
186
157
  }
187
158
 
188
- export const accessToken = {
189
- type: 'secret',
190
- entropy: 112,
191
- charPool: characterPoolSize.alphaNumeric,
192
- otp: false,
193
- expire: 30 * 24 * 60 * 60, // allow override from user
194
- create: async () => randomAlphaNumeric(accessToken.entropy),
195
- encode: async (value, encryptedKey, sub) =>
196
- createSecretHash(value).then((hash) => encrypt(hash, encryptedKey, sub)),
197
- decode: async (value, encryptedKey, sub) => decrypt(value, encryptedKey, sub),
198
- verify: async (value, hash) => verifySecretHash(hash, value)
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
+ })
199
178
  }
200
179
 
201
- // *** Helpers *** //
202
- export const characterLengthToEntropy = (
203
- characterLength,
204
- characterPoolSize
180
+ export const rotateDigestEncryption = (
181
+ encryptedValue,
182
+ encryptionKey,
183
+ newEncryptionKey
205
184
  ) => {
206
- // log_2(characterPoolSize^characterLength)
207
- return Math.round(Math.log2(characterPoolSize ** characterLength))
208
- }
209
-
210
- export const entropyToCharacterLength = (bits, characterPoolSize) => {
211
- // bits*ln(2)/ln(characterPoolSize)
212
- return Math.round((bits * Math.LN2) / Math.log(characterPoolSize))
213
- }
185
+ const digest = symmetricDecrypt(encryptedValue, {
186
+ encryptionKey,
187
+ sub: '',
188
+ encoding: options.symmetricEncryptionEncoding
189
+ })
214
190
 
215
- // *** Digests *** //
216
- export const createDigest = async (value, { algorithm, salt } = {}) => {
217
- algorithm ??= options.digestAlgorithm
218
- salt ??= options.digestSalt
219
- const hash = checksum(algorithm)
220
- .update(value + salt)
221
- .digest('hex')
222
- return `${algorithm}:${hash}`
191
+ return symmetricEncrypt(digest, {
192
+ encryptionKey: newEncryptionKey,
193
+ sub: '',
194
+ encoding: options.symmetricEncryptionEncoding,
195
+ iv: Buffer.from(
196
+ newEncryptionKey.substring(0, symmetricEncryptionEncodingLengths.iv),
197
+ options.symmetricEncryptionEncoding
198
+ )
199
+ })
223
200
  }
224
201
 
225
202
  // *** Hashing *** //
@@ -241,115 +218,231 @@ export const verifySecretHash = async (hash, value) => {
241
218
  return secretVerify(hash, value)
242
219
  }
243
220
 
244
- // *** Encryption *** //
221
+ // *** Symmetric Encryption *** //
245
222
  const authTagLength = 16
246
223
 
247
- export const makeSymetricKey = (assocData) => {
248
- if (!options.encryptionSharedKey) {
224
+ export const symmetricRandomEncryptionKey = () => {
225
+ return randomBytes(32).toString('base64') // 256 bits
226
+ }
227
+
228
+ export const symmetricGenerateEncryptionKey = (sub) => {
229
+ if (!options.symmetricEncryptionKey) {
249
230
  return { encryptionKey: '', encryptedKey: '' }
250
231
  }
251
- const encryptionKey = randomBytes(32)
252
- const encryptedKey = __encrypt(
253
- encryptionKey,
254
- Buffer.from(options.encryptionSharedKey, 'hex'),
255
- assocData,
256
- 'hex',
257
- 'hex'
258
- )
232
+ const encryptionKey = symmetricRandomEncryptionKey()
233
+ const encryptedKey = symmetricEncrypt(encryptionKey, {
234
+ encryptionKey: options.symmetricEncryptionKey,
235
+ sub,
236
+ decoding: 'base64',
237
+ encoding: options.symmetricEncryptionEncoding
238
+ })
259
239
  return { encryptionKey, encryptedKey }
260
240
  }
261
241
 
262
- export const encrypt = (data, encryptedKey, assocData) => {
263
- if (!encryptedKey) return data
264
-
265
- const encryptionKey = __decrypt(
266
- encryptedKey,
267
- Buffer.from(options.encryptionSharedKey, 'hex'),
268
- assocData,
269
- 'hex',
270
- 'hex'
271
- )
272
- return __encrypt(
273
- data,
274
- Buffer.from(encryptionKey, 'hex'),
275
- assocData,
276
- 'utf8',
277
- 'hex'
278
- )
242
+ // sub add context to encryption
243
+ export const symmetricEncryptFields = (
244
+ values,
245
+ { encryptedKey, encryptionKey, sub },
246
+ fields = []
247
+ ) => {
248
+ if (encryptedKey) {
249
+ encryptionKey ??= symmetricDecryptKey(encryptedKey, {
250
+ sub
251
+ })
252
+ }
253
+ if (!encryptionKey) return values
254
+ const encryptedValues = structuredClone(values)
255
+ for (const key of fields) {
256
+ encryptedValues[key] &&= symmetricEncrypt(encryptedValues[key], {
257
+ encryptionKey,
258
+ sub
259
+ })
260
+ }
261
+ return encryptedValues
279
262
  }
280
263
 
281
- const __encrypt = (
264
+ export const symmetricEncrypt = (
282
265
  data,
283
- encryptionKey,
284
- assocData,
285
- decoding = 'utf8',
286
- encoding = 'hex'
266
+ { encryptedKey, encryptionKey, signatureSecret, sub, decoding, encoding, iv }
287
267
  ) => {
288
- const iv = randomBytes(12) // 96 bits
289
- const cipher = createCipheriv(options.encryptionMethod, encryptionKey, iv, {
290
- authTagLength
291
- })
292
- cipher.setAAD(Buffer.from(assocData, 'utf8'))
268
+ if (encryptedKey) {
269
+ encryptionKey ??= symmetricDecryptKey(encryptedKey, {
270
+ sub
271
+ })
272
+ }
273
+ if (!encryptionKey || !data) return data
274
+ decoding ??= 'utf8'
275
+ encoding ??= options.symmetricEncryptionEncoding
276
+ iv ??= randomBytes(12) // 96 bits
277
+
278
+ const cipher = createCipheriv(
279
+ options.symmetricEncryptionMethod,
280
+ Buffer.from(encryptionKey, 'base64'),
281
+ iv,
282
+ {
283
+ authTagLength
284
+ }
285
+ )
286
+ cipher.setAAD(Buffer.from(sub ?? '', 'utf8'))
293
287
  const encryptedData =
294
288
  cipher.update(data, decoding, encoding) + cipher.final(encoding)
295
289
  const authTag = cipher.getAuthTag()
296
- return (
297
- iv.toString(encoding) + // 24 char
298
- authTag.toString(encoding) + // 32 char
299
- encryptedData
300
- )
290
+
291
+ const encryptedDataPacket =
292
+ iv.toString(encoding) + authTag.toString(encoding) + encryptedData
293
+
294
+ // add signature to end
295
+ return symmetricSignatureSign(encryptedDataPacket, signatureSecret)
301
296
  }
302
297
 
303
- export const decrypt = (encryptedData, encryptedKey, assocData) => {
304
- if (!options.encryptionSharedKey || !encryptedKey) return encryptedData
305
- const encryptionKey = __decrypt(
306
- encryptedKey,
307
- Buffer.from(options.encryptionSharedKey, 'hex'),
308
- assocData,
309
- 'hex',
310
- 'hex'
311
- )
312
- const data = __decrypt(
313
- encryptedData,
314
- Buffer.from(encryptionKey, 'hex'),
315
- assocData,
316
- 'hex',
317
- 'utf8'
318
- )
319
- return data
298
+ export const symmetricDecryptFields = (
299
+ encryptedValues,
300
+ { encryptedKey, encryptionKey, signatureSecret, sub },
301
+ fields = []
302
+ ) => {
303
+ if (encryptedKey) {
304
+ encryptionKey ??= symmetricDecryptKey(encryptedKey, {
305
+ sub
306
+ })
307
+ }
308
+ if (!encryptionKey) return encryptedValues
309
+ const values = structuredClone(encryptedValues)
310
+ for (const key of fields) {
311
+ values[key] &&= symmetricDecrypt(values[key], {
312
+ encryptionKey,
313
+ sub
314
+ })
315
+ }
316
+ return values
320
317
  }
321
318
 
322
- const __decrypt = (
323
- data,
324
- encryptionKey,
325
- assocData,
326
- decoding = 'hex',
327
- encoding = 'utf8'
319
+ export const symmetricDecryptKey = (encryptedKey, { sub } = {}) => {
320
+ return symmetricDecrypt(encryptedKey, {
321
+ encryptionKey: options.symmetricEncryptionKey,
322
+ sub,
323
+ decoding: options.symmetricEncryptionEncoding,
324
+ encoding: 'base64'
325
+ })
326
+ }
327
+
328
+ export const symmetricDecrypt = (
329
+ encryptedDataPacket,
330
+ { encryptedKey, encryptionKey, signatureSecret, sub, decoding, encoding }
328
331
  ) => {
329
- const iv = Buffer.from(data.substring(0, 24), decoding)
330
- const authTag = Buffer.from(data.substring(24, 56), decoding)
331
- const encryptedData = Buffer.from(data.substring(56), decoding)
332
+ if (encryptedKey) {
333
+ encryptionKey ??= symmetricDecryptKey(encryptedKey, {
334
+ sub
335
+ })
336
+ }
337
+ if (!encryptionKey || !encryptedDataPacket) return encryptedDataPacket
338
+ decoding ??= options.symmetricEncryptionEncoding
339
+ encoding ??= 'utf8'
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
+
351
+ const iv = Buffer.from(
352
+ encryptedDataPacket.substring(0, symmetricEncryptionEncodingLengths.iv),
353
+ decoding
354
+ )
355
+ const authTag = Buffer.from(
356
+ encryptedDataPacket.substring(
357
+ symmetricEncryptionEncodingLengths.iv,
358
+ symmetricEncryptionEncodingLengths.ivAndAuthTag
359
+ ),
360
+ decoding
361
+ )
362
+ const encryptedData = Buffer.from(
363
+ encryptedDataPacket.substring(
364
+ symmetricEncryptionEncodingLengths.ivAndAuthTag
365
+ ),
366
+ decoding
367
+ )
332
368
 
333
369
  const decipher = createDecipheriv(
334
- options.encryptionMethod,
335
- encryptionKey,
370
+ options.symmetricEncryptionMethod,
371
+ Buffer.from(encryptionKey, 'base64'),
336
372
  iv,
337
373
  {
338
374
  authTagLength
339
375
  }
340
376
  )
341
- decipher.setAAD(Buffer.from(assocData, 'utf8'))
377
+ decipher.setAAD(Buffer.from(sub ?? '', 'utf8'))
378
+
342
379
  decipher.setAuthTag(authTag)
343
- return (
380
+ const data =
344
381
  decipher.update(encryptedData, decoding, encoding) +
345
382
  decipher.final(encoding)
346
- )
383
+ return data
384
+ }
385
+
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
+
412
+ const signedData = data + '.' + signature
413
+ return signedData
347
414
  }
348
415
 
349
- // *** Signatures *** //
350
- export const makeAsymmetricKeys = async (encryptionKey) => {
416
+ export const symmetricSignatureVerify = (
417
+ signedData,
418
+ signatureSecret,
419
+ { algorithm } = {}
420
+ ) => {
421
+ if (typeof signedData !== 'string') return false
422
+ const data = signedData.slice(0, signedData.lastIndexOf('.'))
423
+ const signedDataExpected = symmetricSignatureSign(data, signatureSecret, {
424
+ algorithm
425
+ })
426
+
427
+ return safeEqual(signedData, signedDataExpected) && data
428
+ }
429
+
430
+ export const symmetricRotation = (
431
+ encryptedValues,
432
+ oldOptions, // { encryptedKey, encryptionKey, signatureSecret, sub }, // old
433
+ newOptions, // { encryptedKey, encryptionKey, signatureSecret, sub, decoding, encoding, iv }, // new
434
+ fields = []
435
+ ) => {
436
+ const data = symmetricDecryptFields(encryptedValues, oldOptions, fields)
437
+ const newEncryptedData = symmetricEncryptFields(data, newOptions, fields)
438
+ return newEncryptedData
439
+ }
440
+
441
+ // *** Asymmetric Signatures *** //
442
+ // asymmetricKeyPairType
443
+ export const makeAsymmetricKeys = async () => {
351
444
  const { publicKey, privateKey } = await generateKeyPair('ec', {
352
- namedCurve: 'P-384', // P-512
445
+ namedCurve: options.asymmetricKeyNamedCurve,
353
446
  paramEncoding: 'named',
354
447
  publicKeyEncoding: {
355
448
  type: 'spki',
@@ -357,44 +450,45 @@ export const makeAsymmetricKeys = async (encryptionKey) => {
357
450
  },
358
451
  privateKeyEncoding: {
359
452
  type: 'sec1',
360
- format: 'pem',
361
- // TODO remove encryption for other with sub check?
362
- cipher: options.encryptionMethod,
363
- passphrase: encryptionKey
453
+ format: 'pem'
454
+ // Encryption done at another level for consistency
455
+ // cipher: options.asymmetricEncryptionMethod,
456
+ // passphrase: encryptionKey,
364
457
  }
365
458
  })
366
459
  return { publicKey, privateKey }
367
460
  }
368
461
 
369
- export const makeSignature = (data, privateKey, algorithm = 'SHA3-256') => {
370
- return sign(algorithm, Buffer.from(data), privateKey).toString('base64')
462
+ export const makeAsymmetricSignature = async (
463
+ data,
464
+ privateKey,
465
+ { algorithm } = {}
466
+ ) => {
467
+ algorithm ??= options.asymmetricSignatureAlgorithm
468
+ return (await sign(algorithm, Buffer.from(data), privateKey)).toString(
469
+ options.signatureEncoding
470
+ )
371
471
  }
372
- export const verifySignature = (
472
+ export const verifyAsymmetricSignature = async (
373
473
  data,
374
474
  publicKey,
375
475
  signature,
376
- algorithm = 'SHA3-256'
476
+ { algorithm } = {}
377
477
  ) => {
378
- return verify(
478
+ algorithm ??= options.asymmetricSignatureAlgorithm
479
+ return await verify(
379
480
  algorithm,
380
481
  Buffer.from(data),
381
482
  publicKey,
382
- Buffer.from(signature, 'base64')
483
+ Buffer.from(signature, options.signatureEncoding)
484
+ )
485
+ }
486
+
487
+ export const safeEqual = (input, expected) => {
488
+ const bufferInput = Buffer.from(input)
489
+ const bufferExpected = Buffer.from(expected)
490
+ return (
491
+ bufferInput.length === bufferExpected.length &&
492
+ timingSafeEqual(bufferInput, bufferExpected)
383
493
  )
384
494
  }
385
- // const assocData = 'sub'
386
- // const input = 'data'
387
- //
388
- // const {encryptedKey} = makeSymetricKey(assocData)
389
- // const encryptedData = encrypt(input, encryptedKey, assocData)
390
- // const output = decrypt(encryptedData, encryptedKey, assocData)
391
- // console.log(assocData, input, output, encryptedData)
392
-
393
- // const assocData = 'tlSIcVp1XEF'
394
- // const encryptionKey =
395
- // 'b9ebfdab6603dd8e151138ec8298aaf8b49bf6d38480f9864e9e72e65c4d8b59.b9f9bc89d7f3ce4aa1e1af469d10508b.aa1b6a682fcc186e867a9cfd'
396
- // const value =
397
- // '76ebdea75ea529c16f1645d5435413934cbb97378293776cb446331e6d8323865a808b7f0822cd4f36ef6cca57354ea7c955c94d232a55038321184f4294971315fda4b8dc2f33422bbd3a5c978edc0f5bf019f818f0c92f7c570bdc49fe2f71df3856655198b6723fa52697b9225b2e79f04ed80cd35ef61a8e1e06c3751d3f9caa40aa9a41528ce5ab1bfae8ecff0feab784497ccbf2c7dca4461c6b9a74d1ded6233dc38ad75d0fe2f57cfda2bd1d59ed1f8c53de911cf8071dc7cefde7870c303137586bc0c5f85fe93505d9056994a4d8e59df2027b564c303aa723e98ae5934b15039f90d21827ac22b824593f334466622582a2857f04d8bf450310a9924060d113d2ece0259da68376b31a3c2e8a0b79e81f8efe96bf2b7a6b5b1841edca2566a0a89ab80ee6d6ac2ed24e788ad318a1238dfa5b9e965d256935d3aeafe52f986a530b2f9225bbe7a73b43e6515ce789701f501546239460bccc46285f3fcff0c54c2b6df820334a974abea81460fd4ac99a13cb88e296a2275f80f4c783757319417078458977461dd5e101f9f9b7d303578379dc15ee1b549e3fcceffffba895ecc8291d1f82e150ce8e10640fd79f2e0666167ef7d529cd28cb76a72bf2b3e239dbb845bedd39f42c938bbe711542231fb835edb547d919b3c695272f4cd43640eb3bed2b86948278e5aeeb67ef932ef08332730e75752fd5bd247e2dd30470fc493362d2ecac72237dfbf0a1694f8e6b8c18ab24c71eb723a3f610a056a73fea90f4f8660bfc6dec46fa52a55a3d72a5c9b97770bff7d52ea91b97cc643d915341642e201918fcb088986e9134e133519c6575048e3f840622e569fc5fa66e4f0c31681c9de4b1b1f6acb179728bc0a9f9950bf2e0c8d49da835298013977a2f97d7052bcbedbc39b875bbf9a0622ede589810175ab2455ea72a8274659af7066a2c97ba3aacce84531d8685d08b15d3619038997ae459ac916a88bedaf619f574236d1b86b02f29edaf78684c91c6ee45199b861359ee374fecb95be8a85dc012991d22564adeb2ec95f160574858fa010c40ce74077a08f0e681eb4b10a0093e885f115a12469a82b07012ad4e20b836d5711358c47969af89d09c9299edc540321fe897312ca49c9e8f93d50dc68394d0cc1d108f38e3534821b979c405c1f526a3b57651897873aeeb0909b176fb2034f9fdb1d43a8003809af29ff0bdc456a593029c4526b270908ac94b620a7cb8b7da20a6421e0f2671c899c20d78e1e0a1c3ab0b92948c90fca3c36d21a5e53143bc7e061773251364f8ce3b8992b6313c9604dc19c018b9efc6ca8f7c1fe039077fd76775461d1fa3968f51be7e340e3f09b40d6b44c77a7cbf7d71cad2ddea1121b31d27470a38cda8f8d38170a0d8f0b02d56f8e97b2134597d6111ea6670545bd4f7b4e4d6d7cf2fb5b787a68ba8ee84775f06db195f47282d9646dfbdbb57701e2ef5a432b621cb54a70e94de6b7a3f95bfe3c50793e08a27f76f832070fb2d23ed19dcd75f798aeff0c3e76d8215d43e1a8b7e643bffa243691adf2abcd52f465390e92e0344e2f452673f12d50e2ead0496ba76504ae0f78c81f2a2b5a64b39d3fb35f39f178b9a1587d1f07ed28e7696bfc8740f429f6cc35c964ae04ef39c1070eab6415be3aab887ff6192ac4d49ba9b169221b47d73c55f6010dda07a77cbcf54b779d380591786ba70c6d7000af57034539487f801aeb5c097939682e1530bc26c0b030bcab327ddaaaac5c9b969301b6d4ed7f601a46e8431e8cae3800c80c42686fee3b7625742798ced5976d163d2c8aad1bdac36c001e4e246fdb7b4c6850ed99554cfc0c07b7c7acc7659fd042989cc291ec2bd03a59e439860a5c48e430780f9d85c9acb3e85e741e73163f29afb7c43f143e7ada99a4670cbbe2210bfce8d36ee86.a5237f2d798c9f98b26a676794d3c3f6.0f15d0f53657150aeb1462d5'
398
- // const output = decrypt(value, encryptionKey, assocData)
399
- // console.log(output)
400
- // *** TOTP *** //
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@1auth/crypto",
3
- "version": "0.0.0-alpha.5",
3
+ "version": "0.0.0-alpha.50",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "engines": {
7
- "node": ">=16"
7
+ "node": ">=20"
8
8
  },
9
9
  "engineStrict": true,
10
10
  "publishConfig": {
@@ -27,7 +27,7 @@
27
27
  ],
28
28
  "scripts": {
29
29
  "test": "npm run test:unit",
30
- "test:unit": "ava"
30
+ "test:unit": "node --test"
31
31
  },
32
32
  "license": "MIT",
33
33
  "funding": {
@@ -48,8 +48,8 @@
48
48
  "url": "https://github.com/willfarrell/1auth/issues"
49
49
  },
50
50
  "homepage": "https://github.com/willfarrell/1auth",
51
- "gitHead": "32bb45598025fe664497e4ce8b9c1cf41c75d635",
51
+ "gitHead": "7a6c0fbb8ab71d6a2171e678697de9f237568431",
52
52
  "dependencies": {
53
- "@node-rs/argon2": "1.5.0"
53
+ "@node-rs/argon2": "2.0.2"
54
54
  }
55
55
  }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2023 will Farrell
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.