@1auth/crypto 0.0.0-alpha.60 → 0.0.0-alpha.63
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/package.json +4 -3
- 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.
|
|
3
|
+
"version": "0.0.0-alpha.63",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
"repository": {
|
|
43
43
|
"type": "git",
|
|
44
|
-
"url": "github
|
|
44
|
+
"url": "git+https://github.com/willfarrell/1auth.git",
|
|
45
45
|
"directory": "packages/crypto"
|
|
46
46
|
},
|
|
47
47
|
"bugs": {
|
|
@@ -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
|
-
}
|