@1auth/crypto 0.0.0-alpha.6 → 0.0.0-alpha.60
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 +497 -272
- package/package.json +5 -5
- package/LICENSE +0 -21
package/index.js
CHANGED
|
@@ -1,30 +1,83 @@
|
|
|
1
1
|
import { promisify } from 'node:util'
|
|
2
2
|
import {
|
|
3
3
|
randomBytes,
|
|
4
|
-
createHash
|
|
4
|
+
createHash,
|
|
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
|
+
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'
|
|
21
36
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
|
25
78
|
}
|
|
26
79
|
|
|
27
|
-
const
|
|
80
|
+
export const getOptions = () => options
|
|
28
81
|
|
|
29
82
|
// *** entropy *** //
|
|
30
83
|
// export const characterPoolSize = (value) => {
|
|
@@ -42,184 +95,175 @@ const generateKeyPair = promisify(generateKeyPairCallback)
|
|
|
42
95
|
// return max - min
|
|
43
96
|
// }
|
|
44
97
|
|
|
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)
|
|
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))
|
|
63
103
|
}
|
|
104
|
+
/* export const characterLengthToEntropy = (
|
|
105
|
+
characterLength,
|
|
106
|
+
characterPoolSize,
|
|
107
|
+
) => {
|
|
108
|
+
// log_2(characterPoolSize^characterLength)
|
|
109
|
+
return Math.floor(Math.log2(characterPoolSize ** characterLength));
|
|
110
|
+
}; */
|
|
64
111
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
|
73
152
|
}
|
|
74
153
|
|
|
75
|
-
export const
|
|
76
|
-
|
|
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)
|
|
84
|
-
}
|
|
85
|
-
while (value.length < characterLength) value = '0' + value
|
|
86
|
-
return value
|
|
154
|
+
export const randomAlphaNumeric = (characterLength) => {
|
|
155
|
+
return randomCharacters(characterLength, charactersAlphaNumeric)
|
|
87
156
|
}
|
|
88
157
|
|
|
89
|
-
export const
|
|
90
|
-
|
|
91
|
-
return randomBytes(characterLength).toString('hex')
|
|
158
|
+
export const randomNumeric = (characterLength) => {
|
|
159
|
+
return randomCharacters(characterLength, charactersNumeric)
|
|
92
160
|
}
|
|
93
161
|
|
|
94
162
|
// *** configs *** //
|
|
95
163
|
export const randomId = {
|
|
96
164
|
type: 'id',
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
create:
|
|
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)
|
|
100
169
|
}
|
|
101
170
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
charPool: characterPoolSize.alphaNumeric,
|
|
106
|
-
create: async () => randomAlphaNumeric(subject.entropy)
|
|
171
|
+
// *** Digests *** //
|
|
172
|
+
export const randomChecksumSalt = () => {
|
|
173
|
+
return randomBytes(32) // 256 bits
|
|
107
174
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
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
|
|
160
|
-
}
|
|
161
|
-
|
|
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)
|
|
173
|
-
}
|
|
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)
|
|
186
|
-
}
|
|
187
|
-
|
|
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)
|
|
175
|
+
export const randomChecksumPepper = () => {
|
|
176
|
+
return randomIV() // 96
|
|
199
177
|
}
|
|
200
178
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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 } = {}
|
|
205
190
|
) => {
|
|
206
|
-
|
|
207
|
-
|
|
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
|
|
208
202
|
}
|
|
209
203
|
|
|
210
|
-
export const
|
|
211
|
-
|
|
212
|
-
|
|
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
|
+
)
|
|
213
222
|
}
|
|
214
223
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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}`
|
|
223
267
|
}
|
|
224
268
|
|
|
225
269
|
// *** Hashing *** //
|
|
@@ -241,115 +285,295 @@ export const verifySecretHash = async (hash, value) => {
|
|
|
241
285
|
return secretVerify(hash, value)
|
|
242
286
|
}
|
|
243
287
|
|
|
244
|
-
// *** Encryption *** //
|
|
288
|
+
// *** Symmetric Encryption *** //
|
|
245
289
|
const authTagLength = 16
|
|
246
290
|
|
|
247
|
-
export const
|
|
248
|
-
|
|
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) {
|
|
249
307
|
return { encryptionKey: '', encryptedKey: '' }
|
|
250
308
|
}
|
|
251
|
-
const
|
|
252
|
-
const
|
|
309
|
+
const rowEncryptionKey = symmetricRandomEncryptionKey()
|
|
310
|
+
const rowEncryptedKey = symmetricEncrypt(rowEncryptionKey, {
|
|
253
311
|
encryptionKey,
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
)
|
|
259
|
-
return { encryptionKey, encryptedKey }
|
|
312
|
+
signatureSecret,
|
|
313
|
+
sub
|
|
314
|
+
})
|
|
315
|
+
return { encryptionKey: rowEncryptionKey, encryptedKey: rowEncryptedKey }
|
|
260
316
|
}
|
|
261
317
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
|
279
340
|
}
|
|
280
341
|
|
|
281
|
-
const
|
|
342
|
+
export const symmetricEncrypt = (
|
|
282
343
|
data,
|
|
283
|
-
encryptionKey,
|
|
284
|
-
assocData,
|
|
285
|
-
decoding = 'utf8',
|
|
286
|
-
encoding = 'hex'
|
|
344
|
+
{ encryptedKey, encryptionKey, signatureSecret, sub, decoding, encoding, iv }
|
|
287
345
|
) => {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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)
|
|
293
365
|
const encryptedData =
|
|
294
366
|
cipher.update(data, decoding, encoding) + cipher.final(encoding)
|
|
295
367
|
const authTag = cipher.getAuthTag()
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
368
|
+
|
|
369
|
+
const encryptedDataPacket =
|
|
370
|
+
iv.toString(encoding) + authTag.toString(encoding) + encryptedData
|
|
371
|
+
|
|
372
|
+
// add signature to end
|
|
373
|
+
return symmetricSignatureSign(encryptedDataPacket, { signatureSecret })
|
|
301
374
|
}
|
|
302
375
|
|
|
303
|
-
export const
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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
|
|
318
413
|
)
|
|
319
|
-
return data
|
|
320
414
|
}
|
|
321
415
|
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
encryptionKey,
|
|
325
|
-
assocData,
|
|
326
|
-
decoding = 'hex',
|
|
327
|
-
encoding = 'utf8'
|
|
416
|
+
export const symmetricDecrypt = (
|
|
417
|
+
encryptedDataPacket,
|
|
418
|
+
{ encryptedKey, encryptionKey, signatureSecret, sub, decoding, encoding }
|
|
328
419
|
) => {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
+
)
|
|
332
455
|
|
|
333
456
|
const decipher = createDecipheriv(
|
|
334
|
-
options.
|
|
457
|
+
options.symmetricEncryptionAlgorithm,
|
|
335
458
|
encryptionKey,
|
|
336
459
|
iv,
|
|
337
460
|
{
|
|
338
461
|
authTagLength
|
|
339
462
|
}
|
|
340
463
|
)
|
|
341
|
-
decipher.setAAD(
|
|
464
|
+
decipher.setAAD(sub)
|
|
465
|
+
|
|
342
466
|
decipher.setAuthTag(authTag)
|
|
343
|
-
|
|
467
|
+
const data =
|
|
344
468
|
decipher.update(encryptedData, decoding, encoding) +
|
|
345
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
|
|
346
566
|
)
|
|
567
|
+
newEncryptedValues.encryptionKey = newEncryptedKey
|
|
568
|
+
|
|
569
|
+
return newEncryptedValues
|
|
347
570
|
}
|
|
348
571
|
|
|
349
|
-
// *** Signatures *** //
|
|
350
|
-
|
|
572
|
+
// *** Asymmetric Signatures *** //
|
|
573
|
+
// asymmetricKeyPairType
|
|
574
|
+
export const makeAsymmetricKeys = async () => {
|
|
351
575
|
const { publicKey, privateKey } = await generateKeyPair('ec', {
|
|
352
|
-
namedCurve:
|
|
576
|
+
namedCurve: options.asymmetricKeyNamedCurve,
|
|
353
577
|
paramEncoding: 'named',
|
|
354
578
|
publicKeyEncoding: {
|
|
355
579
|
type: 'spki',
|
|
@@ -357,44 +581,45 @@ export const makeAsymmetricKeys = async (encryptionKey) => {
|
|
|
357
581
|
},
|
|
358
582
|
privateKeyEncoding: {
|
|
359
583
|
type: 'sec1',
|
|
360
|
-
format: 'pem'
|
|
361
|
-
//
|
|
362
|
-
cipher: options.
|
|
363
|
-
passphrase: encryptionKey
|
|
584
|
+
format: 'pem'
|
|
585
|
+
// Encryption done at another level for consistency
|
|
586
|
+
// cipher: options.asymmetricEncryptionAlgorithm,
|
|
587
|
+
// passphrase: encryptionKey,
|
|
364
588
|
}
|
|
365
589
|
})
|
|
366
590
|
return { publicKey, privateKey }
|
|
367
591
|
}
|
|
368
592
|
|
|
369
|
-
export const
|
|
370
|
-
|
|
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
|
+
)
|
|
371
602
|
}
|
|
372
|
-
export const
|
|
603
|
+
export const verifyAsymmetricSignature = async (
|
|
373
604
|
data,
|
|
374
605
|
publicKey,
|
|
375
606
|
signature,
|
|
376
|
-
|
|
607
|
+
{ hashAlgorithm } = {}
|
|
377
608
|
) => {
|
|
378
|
-
|
|
379
|
-
|
|
609
|
+
hashAlgorithm ??= options.asymmetricSignatureHashAlgorithm
|
|
610
|
+
return await verify(
|
|
611
|
+
hashAlgorithm,
|
|
380
612
|
Buffer.from(data),
|
|
381
613
|
publicKey,
|
|
382
|
-
Buffer.from(signature,
|
|
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)
|
|
383
624
|
)
|
|
384
625
|
}
|
|
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.60",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
|
-
"node": ">=
|
|
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": "
|
|
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": "
|
|
51
|
+
"gitHead": "7a6c0fbb8ab71d6a2171e678697de9f237568431",
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@node-rs/argon2": "
|
|
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.
|