@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.
- package/index.js +315 -270
- package/package.json +4 -8
- 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
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
24
|
-
|
|
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
|
|
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
|
-
// ***
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
90
|
-
const characterLength = entropyToCharacterLength(bits, characterPoolSize.hex)
|
|
91
|
-
return randomBytes(characterLength).toString('hex')
|
|
118
|
+
return string
|
|
92
119
|
}
|
|
93
120
|
|
|
94
|
-
|
|
95
|
-
|
|
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
|
|
103
|
-
|
|
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
|
|
110
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
//
|
|
163
|
-
export const
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
173
|
+
export const rotateDigestEncryption = (
|
|
174
|
+
encryptedValue,
|
|
175
|
+
encryptionKey,
|
|
176
|
+
newEncryptionKey
|
|
205
177
|
) => {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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 = (
|
|
248
|
-
if (!options.
|
|
217
|
+
export const makeSymetricKey = (sub) => {
|
|
218
|
+
if (!options.symetricEncryptionKey) {
|
|
249
219
|
return { encryptionKey: '', encryptedKey: '' }
|
|
250
220
|
}
|
|
251
|
-
const encryptionKey =
|
|
252
|
-
const encryptedKey =
|
|
253
|
-
encryptionKey,
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
|
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
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
279
|
+
|
|
280
|
+
const encryptedDataPacket =
|
|
281
|
+
iv.toString(encoding) + authTag.toString(encoding) + encryptedData
|
|
282
|
+
|
|
283
|
+
return encryptedDataPacket
|
|
301
284
|
}
|
|
302
285
|
|
|
303
|
-
export const
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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.
|
|
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(
|
|
355
|
+
decipher.setAAD(Buffer.from(sub ?? '', 'utf8'))
|
|
356
|
+
|
|
342
357
|
decipher.setAuthTag(authTag)
|
|
343
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
362
|
-
cipher: options.
|
|
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
|
|
370
|
-
|
|
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
|
|
395
|
+
export const verifyAsymmetricSignature = async (
|
|
373
396
|
data,
|
|
374
397
|
publicKey,
|
|
375
398
|
signature,
|
|
376
|
-
algorithm =
|
|
399
|
+
{ algorithm } = {}
|
|
377
400
|
) => {
|
|
378
|
-
|
|
401
|
+
algorithm ??= options.digestAlgorithm
|
|
402
|
+
return await verify(
|
|
379
403
|
algorithm,
|
|
380
404
|
Buffer.from(data),
|
|
381
405
|
publicKey,
|
|
382
|
-
Buffer.from(signature,
|
|
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.
|
|
3
|
+
"version": "0.0.0-alpha.41",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
|
-
"node": ">=
|
|
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": "
|
|
47
|
+
"gitHead": "7a6c0fbb8ab71d6a2171e678697de9f237568431",
|
|
52
48
|
"dependencies": {
|
|
53
|
-
"@node-rs/argon2": "
|
|
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.
|