@1auth/crypto 0.0.0-alpha.4 → 0.0.0-alpha.41

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 +315 -270
  2. package/package.json +4 -8
  3. package/LICENSE +0 -21
package/index.js CHANGED
@@ -4,27 +4,47 @@ 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
+ // symetricEncryptionKey: randomBytes(32).toString('base64') // 256 bits
22
+ symetricEncryptionKey: undefined,
23
+ symetricEncryptionMethod: 'chacha20-poly1305', // 2024-05: AES-256 GCM (aes-256-gcm) or ChaCha20-Poly1305 (chacha20-poly1305)
24
+ symetricEncryptionEncoding: 'base64', // https://nodejs.org/api/buffer.html#buffers-and-character-encodings
25
+ asymetricKey: 'P-384',
26
+ digestAlgorithm: 'sha3-384',
27
+ hmacAlgorithm: 'sha256',
28
+ signatureEncoding: 'base64'
21
29
  }
22
-
23
- export default (params) => {
24
- Object.assign(options, params)
30
+ const symetricEncryptionEncodingLengths = {}
31
+ const options = {}
32
+ export default (opt = {}) => {
33
+ Object.assign(options, defaults, opt)
34
+ symetricEncryptionEncodingLengths.iv = randomBytes(12).toString(
35
+ options.symetricEncryptionEncoding
36
+ ).length
37
+ symetricEncryptionEncodingLengths.ivAndAuthTag =
38
+ symetricEncryptionEncodingLengths.iv +
39
+ randomBytes(16).toString(options.symetricEncryptionEncoding).length
40
+ if (!options.symetricEncryptionKey) {
41
+ console.warn(
42
+ "@1auth/crypto symetricEncryptionKey is empty, use a stored secret made from randomBytes(32).toString('base64'). Encryption disabled."
43
+ )
44
+ }
25
45
  }
26
46
 
27
- const generateKeyPair = promisify(generateKeyPairCallback)
47
+ export const getOptions = () => options
28
48
 
29
49
  // *** entropy *** //
30
50
  // export const characterPoolSize = (value) => {
@@ -42,184 +62,134 @@ const generateKeyPair = promisify(generateKeyPairCallback)
42
62
  // return max - min
43
63
  // }
44
64
 
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('=', '')
65
+ // *** Helpers *** //
66
+ // Ref: https://therootcompany.com/blog/how-many-bits-of-entropy-per-character/
67
+ export const entropyToCharacterLength = (bits, characterPoolSize) => {
68
+ // bits*ln(2)/ln(characterPoolSize)
69
+ return Math.ceil((bits * Math.LN2) / Math.log(characterPoolSize))
73
70
  }
71
+ /* export const characterLengthToEntropy = (
72
+ characterLength,
73
+ characterPoolSize,
74
+ ) => {
75
+ // log_2(characterPoolSize^characterLength)
76
+ return Math.floor(Math.log2(characterPoolSize ** characterLength));
77
+ }; */
74
78
 
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)
79
+ // *** Random generators *** //
80
+ // Ref: https://github.com/sindresorhus/crypto-random-string/blob/main/core.js
81
+ export const charactersAlphaNumeric = [
82
+ ...'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
83
+ ]
84
+ export const charactersDistinguishable = [...'CDEHKMPRTUWXY012458']
85
+ export const charactersNumeric = [...'0123456789']
86
+
87
+ export const randomCharacters = (
88
+ length,
89
+ characters = charactersAlphaNumeric
90
+ ) => {
91
+ // Generating entropy is faster than complex math operations, so we use the simplest way
92
+ const characterCount = characters.length
93
+ const maxValidSelector =
94
+ Math.floor(0x1_00_00 / characterCount) * characterCount - 1 // Using values above this will ruin distribution when using modular division
95
+ 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
96
+ let string = ''
97
+ let stringLength = 0
98
+
99
+ while (stringLength < length) {
100
+ // In case we had many bad values, which may happen for character sets of size above 0x8000 but close to it
101
+ const entropy = new Uint8Array(randomBytes(entropyLength))
102
+ let entropyPosition = 0
103
+
104
+ while (entropyPosition < entropyLength && stringLength < length) {
105
+ const entropyValue =
106
+ entropy[entropyPosition] + (entropy[entropyPosition + 1] << 8) // eslint-disable-line no-bitwise
107
+ entropyPosition += 2
108
+ if (entropyValue > maxValidSelector) {
109
+ // Skip values which will ruin distribution when using modular division
110
+ continue
111
+ }
112
+
113
+ string += characters[entropyValue % characterCount]
114
+ stringLength++
115
+ }
84
116
  }
85
- while (value.length < characterLength) value = '0' + value
86
- return value
87
- }
88
117
 
89
- export const randomHex = (bits) => {
90
- const characterLength = entropyToCharacterLength(bits, characterPoolSize.hex)
91
- return randomBytes(characterLength).toString('hex')
118
+ return string
92
119
  }
93
120
 
94
- // *** configs *** //
95
- export const randomId = {
96
- type: 'id',
97
- entropy: 64,
98
- charPool: characterPoolSize.alphaNumeric,
99
- create: async () => randomAlphaNumeric(randomId.entropy)
121
+ export const randomAlphaNumeric = (characterLength) => {
122
+ return randomCharacters(characterLength, charactersAlphaNumeric)
100
123
  }
101
124
 
102
- export const subject = {
103
- type: 'id',
104
- entropy: 64,
105
- charPool: characterPoolSize.alphaNumeric,
106
- create: async () => randomAlphaNumeric(subject.entropy)
125
+ export const randomNumeric = (characterLength) => {
126
+ return randomCharacters(characterLength, charactersNumeric)
107
127
  }
108
128
 
109
- export const session = {
110
- type: 'id',
111
- entropy: 128, // ASVS 3.2.2
112
- charPool: characterPoolSize.alphaNumeric,
113
- expire: 15 * 60,
114
- create: async () => randomAlphaNumeric(session.entropy)
129
+ export const randomSymetricEncryptionKey = () => {
130
+ return randomBytes(32).toString('base64') // 256 bits
115
131
  }
116
132
 
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
133
+ // *** configs *** //
134
+ export const randomId = {
135
+ type: 'id',
136
+ minLength: entropyToCharacterLength(64, charactersAlphaNumeric.length),
137
+ // TODO update to use https://github.com/jetpack-io/typeid
138
+ create: (prefix) =>
139
+ (prefix ? prefix + '_' : '') + randomAlphaNumeric(randomId.minLength)
160
140
  }
161
141
 
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)
142
+ // *** Digests *** //
143
+ export const createChecksum = (value, { algorithm } = {}) => {
144
+ algorithm ??= options.digestAlgorithm
145
+ return checksum(algorithm).update(value).digest('hex')
173
146
  }
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)
147
+ export const createDigest = (value, { algorithm } = {}) => {
148
+ algorithm ??= options.digestAlgorithm
149
+ const checksum = createChecksum(value, { algorithm })
150
+ return `${algorithm}:${checksum}`
186
151
  }
187
152
 
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)
153
+ // TODO evaluate non-iv encryption approach?
154
+ // Use encryption as pepper to allow easier rotation of pepper
155
+ export const createEncryptedDigest = (value, { algorithm } = {}) => {
156
+ const digest = createDigest(value, { algorithm })
157
+ // encrypting using the symetricEncryptionKey instread of
158
+ // the row encryptionKey to allow lookup, must have fixed iv
159
+ return symetricEncrypt(digest, {
160
+ encryptionKey: options.symetricEncryptionKey,
161
+ sub: '',
162
+ encoding: options.symetricEncryptionEncoding,
163
+ iv: Buffer.from(
164
+ options.symetricEncryptionKey.substring(
165
+ 0,
166
+ symetricEncryptionEncodingLengths.iv
167
+ ),
168
+ options.symetricEncryptionEncoding
169
+ )
170
+ })
199
171
  }
200
172
 
201
- // *** Helpers *** //
202
- export const characterLengthToEntropy = (
203
- characterLength,
204
- characterPoolSize
173
+ export const rotateDigestEncryption = (
174
+ encryptedValue,
175
+ encryptionKey,
176
+ newEncryptionKey
205
177
  ) => {
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
- }
178
+ const digest = symetricDecrypt(encryptedValue, {
179
+ encryptionKey,
180
+ sub: '',
181
+ encoding: options.symetricEncryptionEncoding
182
+ })
214
183
 
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}`
184
+ return symetricEncrypt(digest, {
185
+ encryptionKey: newEncryptionKey,
186
+ sub: '',
187
+ encoding: options.symetricEncryptionEncoding,
188
+ iv: Buffer.from(
189
+ newEncryptionKey.substring(0, symetricEncryptionEncodingLengths.iv),
190
+ options.symetricEncryptionEncoding
191
+ )
192
+ })
223
193
  }
224
194
 
225
195
  // *** Hashing *** //
@@ -244,110 +214,156 @@ export const verifySecretHash = async (hash, value) => {
244
214
  // *** Encryption *** //
245
215
  const authTagLength = 16
246
216
 
247
- export const makeSymetricKey = (assocData) => {
248
- if (!options.encryptionSharedKey) {
217
+ export const makeSymetricKey = (sub) => {
218
+ if (!options.symetricEncryptionKey) {
249
219
  return { encryptionKey: '', encryptedKey: '' }
250
220
  }
251
- const encryptionKey = randomBytes(32)
252
- const encryptedKey = __encrypt(
253
- encryptionKey,
254
- Buffer.from(options.encryptionSharedKey, 'hex'),
255
- assocData,
256
- 'hex',
257
- 'hex'
258
- )
221
+ const encryptionKey = randomSymetricEncryptionKey()
222
+ const encryptedKey = symetricEncrypt(encryptionKey, {
223
+ encryptionKey: options.symetricEncryptionKey,
224
+ sub,
225
+ decoding: 'base64',
226
+ encoding: options.symetricEncryptionEncoding
227
+ })
259
228
  return { encryptionKey, encryptedKey }
260
229
  }
261
230
 
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
- )
231
+ // sub add context to encryption
232
+ export const symetricEncryptFields = (
233
+ values,
234
+ { encryptedKey, encryptionKey, sub },
235
+ fields = []
236
+ ) => {
237
+ if (encryptedKey) {
238
+ encryptionKey ??= symetricDecryptKey(encryptedKey, {
239
+ sub
240
+ })
241
+ }
242
+ if (!encryptionKey) return values
243
+ const encryptedValues = structuredClone(values)
244
+ for (const key of fields) {
245
+ encryptedValues[key] &&= symetricEncrypt(encryptedValues[key], {
246
+ encryptionKey,
247
+ sub
248
+ })
249
+ }
250
+ return encryptedValues
279
251
  }
280
252
 
281
- const __encrypt = (
253
+ export const symetricEncrypt = (
282
254
  data,
283
- encryptionKey,
284
- assocData,
285
- decoding = 'utf8',
286
- encoding = 'hex'
255
+ { encryptedKey, encryptionKey, sub, decoding, encoding, iv }
287
256
  ) => {
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'))
257
+ if (encryptedKey) {
258
+ encryptionKey ??= symetricDecryptKey(encryptedKey, {
259
+ sub
260
+ })
261
+ }
262
+ if (!encryptionKey || !data) return data
263
+ decoding ??= 'utf8'
264
+ encoding ??= options.symetricEncryptionEncoding
265
+ iv ??= randomBytes(12) // 96 bits
266
+
267
+ const cipher = createCipheriv(
268
+ options.symetricEncryptionMethod,
269
+ Buffer.from(encryptionKey, 'base64'),
270
+ iv,
271
+ {
272
+ authTagLength
273
+ }
274
+ )
275
+ cipher.setAAD(Buffer.from(sub ?? '', 'utf8'))
293
276
  const encryptedData =
294
277
  cipher.update(data, decoding, encoding) + cipher.final(encoding)
295
278
  const authTag = cipher.getAuthTag()
296
- return (
297
- iv.toString(encoding) + // 24 char
298
- authTag.toString(encoding) + // 32 char
299
- encryptedData
300
- )
279
+
280
+ const encryptedDataPacket =
281
+ iv.toString(encoding) + authTag.toString(encoding) + encryptedData
282
+
283
+ return encryptedDataPacket
301
284
  }
302
285
 
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
286
+ export const symetricDecryptFields = (
287
+ encryptedValues,
288
+ { encryptedKey, encryptionKey, sub },
289
+ fields = []
290
+ ) => {
291
+ if (encryptedKey) {
292
+ encryptionKey ??= symetricDecryptKey(encryptedKey, {
293
+ sub
294
+ })
295
+ }
296
+ if (!encryptionKey) return encryptedValues
297
+ const values = structuredClone(encryptedValues)
298
+ for (const key of fields) {
299
+ values[key] &&= symetricDecrypt(values[key], {
300
+ encryptionKey,
301
+ sub
302
+ })
303
+ }
304
+ return values
320
305
  }
321
306
 
322
- const __decrypt = (
323
- data,
324
- encryptionKey,
325
- assocData,
326
- decoding = 'hex',
327
- encoding = 'utf8'
307
+ export const symetricDecryptKey = (encryptedKey, { sub } = {}) => {
308
+ return symetricDecrypt(encryptedKey, {
309
+ encryptionKey: options.symetricEncryptionKey,
310
+ sub,
311
+ decoding: options.symetricEncryptionEncoding,
312
+ encoding: 'base64'
313
+ })
314
+ }
315
+
316
+ export const symetricDecrypt = (
317
+ encryptedDataPacket,
318
+ { encryptedKey, encryptionKey, sub, decoding, encoding }
328
319
  ) => {
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)
320
+ if (encryptedKey) {
321
+ encryptionKey ??= symetricDecryptKey(encryptedKey, {
322
+ sub
323
+ })
324
+ }
325
+ if (!encryptionKey || !encryptedDataPacket) return encryptedDataPacket
326
+ decoding ??= options.symetricEncryptionEncoding
327
+ encoding ??= 'utf8'
328
+
329
+ const iv = Buffer.from(
330
+ encryptedDataPacket.substring(0, symetricEncryptionEncodingLengths.iv),
331
+ decoding
332
+ )
333
+ const authTag = Buffer.from(
334
+ encryptedDataPacket.substring(
335
+ symetricEncryptionEncodingLengths.iv,
336
+ symetricEncryptionEncodingLengths.ivAndAuthTag
337
+ ),
338
+ decoding
339
+ )
340
+ const encryptedData = Buffer.from(
341
+ encryptedDataPacket.substring(
342
+ symetricEncryptionEncodingLengths.ivAndAuthTag
343
+ ),
344
+ decoding
345
+ )
332
346
 
333
347
  const decipher = createDecipheriv(
334
- options.encryptionMethod,
335
- encryptionKey,
348
+ options.symetricEncryptionMethod,
349
+ Buffer.from(encryptionKey, 'base64'),
336
350
  iv,
337
351
  {
338
352
  authTagLength
339
353
  }
340
354
  )
341
- decipher.setAAD(Buffer.from(assocData, 'utf8'))
355
+ decipher.setAAD(Buffer.from(sub ?? '', 'utf8'))
356
+
342
357
  decipher.setAuthTag(authTag)
343
- return (
358
+ const data =
344
359
  decipher.update(encryptedData, decoding, encoding) +
345
360
  decipher.final(encoding)
346
- )
361
+ return data
347
362
  }
348
363
 
349
364
  // *** Signatures *** //
350
- export const makeAsymmetricKeys = async (encryptionKey) => {
365
+ // asymmetricKeyPairType
366
+ export const makeAsymmetricKeys = async () => {
351
367
  const { publicKey, privateKey } = await generateKeyPair('ec', {
352
368
  namedCurve: 'P-384', // P-512
353
369
  paramEncoding: 'named',
@@ -357,44 +373,73 @@ export const makeAsymmetricKeys = async (encryptionKey) => {
357
373
  },
358
374
  privateKeyEncoding: {
359
375
  type: 'sec1',
360
- format: 'pem',
361
- // TODO remove encryption for other with sub check?
362
- cipher: options.encryptionMethod,
363
- passphrase: encryptionKey
376
+ format: 'pem'
377
+ // Encryption done at another level for consistency
378
+ // cipher: options.asymetricEncryptionMethod,
379
+ // passphrase: encryptionKey,
364
380
  }
365
381
  })
366
382
  return { publicKey, privateKey }
367
383
  }
368
384
 
369
- export const makeSignature = (data, privateKey, algorithm = 'SHA3-256') => {
370
- return sign(algorithm, Buffer.from(data), privateKey).toString('base64')
385
+ export const makeAsymmetricSignature = async (
386
+ data,
387
+ privateKey,
388
+ { algorithm } = {}
389
+ ) => {
390
+ algorithm ??= options.digestAlgorithm
391
+ return (await sign(algorithm, Buffer.from(data), privateKey)).toString(
392
+ options.signatureEncoding
393
+ )
371
394
  }
372
- export const verifySignature = (
395
+ export const verifyAsymmetricSignature = async (
373
396
  data,
374
397
  publicKey,
375
398
  signature,
376
- algorithm = 'SHA3-256'
399
+ { algorithm } = {}
377
400
  ) => {
378
- return verify(
401
+ algorithm ??= options.digestAlgorithm
402
+ return await verify(
379
403
  algorithm,
380
404
  Buffer.from(data),
381
405
  publicKey,
382
- Buffer.from(signature, 'base64')
406
+ Buffer.from(signature, options.signatureEncoding)
407
+ )
408
+ }
409
+
410
+ export const makeSymmetricSignature = (
411
+ data,
412
+ encryptionKey,
413
+ { algorithm } = {}
414
+ ) => {
415
+ encryptionKey ??= options.symetricEncryptionKey
416
+ algorithm ??= options.hmacAlgorithm
417
+ const signature = createHmac('sha256', encryptionKey)
418
+ .update(data)
419
+ .digest('base64')
420
+ .replace(/=+$/, '')
421
+ const signedData = data + '.' + signature
422
+ return signedData
423
+ }
424
+
425
+ export const verifySymmetricSignature = (
426
+ signedData,
427
+ encryptionKey,
428
+ { algorithm } = {}
429
+ ) => {
430
+ const data = signedData.slice(0, signedData.lastIndexOf('.'))
431
+ const signedDataExpected = makeSymmetricSignature(data, encryptionKey, {
432
+ algorithm
433
+ })
434
+
435
+ return safeEqual(signedData, signedDataExpected) && data
436
+ }
437
+
438
+ export const safeEqual = (input, expected) => {
439
+ const bufferInput = Buffer.from(input)
440
+ const bufferExpected = Buffer.from(expected)
441
+ return (
442
+ bufferInput.length === bufferExpected.length &&
443
+ timingSafeEqual(bufferInput, bufferExpected)
383
444
  )
384
445
  }
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.4",
3
+ "version": "0.0.0-alpha.41",
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": {
@@ -25,10 +25,6 @@
25
25
  "index.js",
26
26
  "index.d.ts"
27
27
  ],
28
- "scripts": {
29
- "test": "npm run test:unit",
30
- "test:unit": "ava"
31
- },
32
28
  "license": "MIT",
33
29
  "funding": {
34
30
  "type": "github",
@@ -48,8 +44,8 @@
48
44
  "url": "https://github.com/willfarrell/1auth/issues"
49
45
  },
50
46
  "homepage": "https://github.com/willfarrell/1auth",
51
- "gitHead": "47894073ee5f1b6754cef5e482f123dc81575b1c",
47
+ "gitHead": "7a6c0fbb8ab71d6a2171e678697de9f237568431",
52
48
  "dependencies": {
53
- "@node-rs/argon2": "1.5.0"
49
+ "@node-rs/argon2": "2.0.0"
54
50
  }
55
51
  }
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.