@1auth/crypto 0.0.0-alpha.7 → 0.0.0-alpha.70
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 +553 -338
- package/package.json +7 -6
- package/LICENSE +0 -21
package/index.js
CHANGED
|
@@ -1,30 +1,85 @@
|
|
|
1
|
-
import { promisify } from
|
|
1
|
+
import { promisify } from "node:util";
|
|
2
2
|
import {
|
|
3
3
|
randomBytes,
|
|
4
|
-
|
|
4
|
+
randomInt,
|
|
5
|
+
createHash,
|
|
5
6
|
createCipheriv,
|
|
6
7
|
createDecipheriv,
|
|
8
|
+
createHmac,
|
|
9
|
+
timingSafeEqual,
|
|
7
10
|
generateKeyPair as generateKeyPairCallback,
|
|
8
|
-
sign,
|
|
9
|
-
verify
|
|
10
|
-
} from
|
|
11
|
+
sign as signCallback,
|
|
12
|
+
verify as verifyCallback,
|
|
13
|
+
} from "node:crypto";
|
|
11
14
|
// https://github.com/napi-rs/node-rs/tree/main/packages/argon2
|
|
12
|
-
import { hash as secretHash, verify as secretVerify } from
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
import { hash as secretHash, verify as secretVerify } from "@node-rs/argon2";
|
|
16
|
+
import { customAlphabet } from "nanoid";
|
|
17
|
+
|
|
18
|
+
const generateKeyPair = promisify(generateKeyPairCallback);
|
|
19
|
+
const sign = promisify(signCallback);
|
|
20
|
+
const verify = promisify(verifyCallback);
|
|
21
|
+
|
|
22
|
+
const defaults = {
|
|
23
|
+
symmetricEncryptionKey: undefined, // symmetricRandomEncryptionKey()
|
|
24
|
+
symmetricEncryptionAlgorithm: "chacha20-poly1305", // 2025-03: AES-256 GCM (aes-256-gcm) or ChaCha20-Poly1305 (chacha20-poly1305)
|
|
25
|
+
symmetricEncryptionEncoding: undefined, // https://nodejs.org/api/buffer.html#buffers-and-character-encodings
|
|
26
|
+
symmetricSignatureHashAlgorithm: undefined, // fallback to defaultHashAlgorithm
|
|
27
|
+
symmetricSignatureSecret: undefined, // symmetricRandomSignatureSecret()
|
|
28
|
+
symmetricSignatureEncoding: undefined, // fallback to defaultEncoding
|
|
29
|
+
asymmetricKeyNamedCurve: "P-384", // P-512
|
|
30
|
+
asymmetricSignatureHashAlgorithm: undefined, // fallback to defaultHashAlgorithm
|
|
31
|
+
asymmetricSignatureEncoding: undefined, // fallback to defaultEncoding
|
|
32
|
+
digestChecksumHashAlgorithm: undefined, // fallback to defaultHashAlgorithm
|
|
33
|
+
digestChecksumEncoding: undefined,
|
|
34
|
+
digestChecksumSalt: undefined, // randomChecksumSalt()
|
|
35
|
+
digestChecksumPepper: undefined, // randomChecksumPepper()
|
|
36
|
+
defaultEncoding: "base64",
|
|
37
|
+
defaultHashAlgorithm: "sha3-384",
|
|
38
|
+
};
|
|
39
|
+
const symmetricEncryptionEncodingLengths = {};
|
|
40
|
+
const options = {};
|
|
41
|
+
export default (opt = {}) => {
|
|
42
|
+
Object.assign(options, defaults, opt);
|
|
43
|
+
|
|
44
|
+
// Check options, set defaults
|
|
45
|
+
if (!options.symmetricEncryptionKey) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
"@1auth/crypto symmetricEncryptionKey is empty, use a stored secret made from randomBytes(32) Encryption disabled.",
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
options.symmetricEncryptionEncoding ??= options.defaultEncoding;
|
|
51
|
+
options.symmetricSignatureHashAlgorithm ??= options.defaultHashAlgorithm;
|
|
52
|
+
if (!options.symmetricSignatureSecret) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
"@1auth/crypto symmetricSignatureSecret is empty, use a stored secret made from randomBytes(32) Signature disabled.",
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
options.symmetricSignatureEncoding ??= options.defaultEncoding;
|
|
58
|
+
options.asymmetricSignatureHashAlgorithm ??= options.defaultHashAlgorithm;
|
|
59
|
+
options.asymmetricSignatureEncoding ??= options.defaultEncoding;
|
|
60
|
+
if (!options.digestChecksumSalt) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
"@1auth/crypto digestChecksumSalt is empty, use a stored secret made from randomBytes(32) Checksum salting disabled.",
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
if (!options.digestChecksumPepper) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
"@1auth/crypto digestChecksumPepper is empty, use a stored secret made from randomBytes(12) Checksum peppering disabled.",
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
options.digestChecksumHashAlgorithm ??= options.defaultHashAlgorithm;
|
|
71
|
+
options.digestChecksumEncoding ??= options.defaultEncoding;
|
|
22
72
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
73
|
+
// Lengths
|
|
74
|
+
symmetricEncryptionEncodingLengths.iv = randomIV().toString(
|
|
75
|
+
options.symmetricEncryptionEncoding,
|
|
76
|
+
).length;
|
|
77
|
+
symmetricEncryptionEncodingLengths.ivAndAuthTag =
|
|
78
|
+
symmetricEncryptionEncodingLengths.iv +
|
|
79
|
+
randomBytes(16).toString(options.symmetricEncryptionEncoding).length;
|
|
80
|
+
};
|
|
26
81
|
|
|
27
|
-
const
|
|
82
|
+
export const getOptions = () => options;
|
|
28
83
|
|
|
29
84
|
// *** entropy *** //
|
|
30
85
|
// export const characterPoolSize = (value) => {
|
|
@@ -42,359 +97,519 @@ const generateKeyPair = promisify(generateKeyPairCallback)
|
|
|
42
97
|
// return max - min
|
|
43
98
|
// }
|
|
44
99
|
|
|
45
|
-
// *** Random generators *** //
|
|
46
|
-
export const characterPoolSize = {
|
|
47
|
-
keyboard: 94, // (26 + 10 + 11) * 2
|
|
48
|
-
alphaNumeric: 62, // (26 + 10) * 2
|
|
49
|
-
base64: 64, // (26 + 10) * 2 + 2
|
|
50
|
-
hex: 16,
|
|
51
|
-
numeric: 10
|
|
52
|
-
}
|
|
53
|
-
// Alt: https://github.com/sindresorhus/crypto-random-string/blob/main/core.js
|
|
54
|
-
export const randomAlphaNumeric = (bits) => {
|
|
55
|
-
const characterLength = entropyToCharacterLength(
|
|
56
|
-
bits,
|
|
57
|
-
characterPoolSize.alphaNumeric
|
|
58
|
-
)
|
|
59
|
-
return randomBytes(characterLength * 2)
|
|
60
|
-
.toString('base64')
|
|
61
|
-
.replace(/[^a-zA-Z0-9]/g, '')
|
|
62
|
-
.substring(0, characterLength)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export const randomBase64 = (bits) => {
|
|
66
|
-
const characterLength = entropyToCharacterLength(
|
|
67
|
-
bits,
|
|
68
|
-
characterPoolSize.base64
|
|
69
|
-
)
|
|
70
|
-
return randomBytes(characterLength + 1)
|
|
71
|
-
.toString('base64')
|
|
72
|
-
.replace('=', '')
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export const randomNumeric = (bits) => {
|
|
76
|
-
const characterLength = entropyToCharacterLength(
|
|
77
|
-
bits,
|
|
78
|
-
characterPoolSize.numeric
|
|
79
|
-
)
|
|
80
|
-
const max = 10 ** characterLength
|
|
81
|
-
let value = Math.floor(Math.random() * max).toString()
|
|
82
|
-
if (value === max.toString()) {
|
|
83
|
-
return randomNumeric(bits)
|
|
84
|
-
}
|
|
85
|
-
while (value.length < characterLength) value = '0' + value
|
|
86
|
-
return value
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export const randomHex = (bits) => {
|
|
90
|
-
const characterLength = entropyToCharacterLength(bits, characterPoolSize.hex)
|
|
91
|
-
return randomBytes(characterLength).toString('hex')
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// *** configs *** //
|
|
95
|
-
export const randomId = {
|
|
96
|
-
type: 'id',
|
|
97
|
-
entropy: 64,
|
|
98
|
-
charPool: characterPoolSize.alphaNumeric,
|
|
99
|
-
create: async () => randomAlphaNumeric(randomId.entropy)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export const subject = {
|
|
103
|
-
type: 'id',
|
|
104
|
-
entropy: 64,
|
|
105
|
-
charPool: characterPoolSize.alphaNumeric,
|
|
106
|
-
create: async () => randomAlphaNumeric(subject.entropy)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export const session = {
|
|
110
|
-
type: 'id',
|
|
111
|
-
entropy: 128, // ASVS 3.2.2
|
|
112
|
-
charPool: characterPoolSize.alphaNumeric,
|
|
113
|
-
expire: 15 * 60,
|
|
114
|
-
create: async () => randomAlphaNumeric(session.entropy)
|
|
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)
|
|
199
|
-
}
|
|
200
|
-
|
|
201
100
|
// *** Helpers *** //
|
|
202
|
-
|
|
101
|
+
// Ref: https://therootcompany.com/blog/how-many-bits-of-entropy-per-character/
|
|
102
|
+
export const entropyToCharacterLength = (bits, characterPoolSize) => {
|
|
103
|
+
// bits*ln(2)/ln(characterPoolSize)
|
|
104
|
+
return Math.ceil((bits * Math.LN2) / Math.log(characterPoolSize));
|
|
105
|
+
};
|
|
106
|
+
/* export const characterLengthToEntropy = (
|
|
203
107
|
characterLength,
|
|
204
|
-
characterPoolSize
|
|
108
|
+
characterPoolSize,
|
|
205
109
|
) => {
|
|
206
110
|
// log_2(characterPoolSize^characterLength)
|
|
207
|
-
return Math.
|
|
208
|
-
}
|
|
111
|
+
return Math.floor(Math.log2(characterPoolSize ** characterLength));
|
|
112
|
+
}; */
|
|
209
113
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
114
|
+
// *** Random generators *** //
|
|
115
|
+
export { randomBytes, randomInt, randomUUID } from "node:crypto";
|
|
116
|
+
|
|
117
|
+
export const charactersNumeric = "0123456789";
|
|
118
|
+
export const charactersAlphaUpper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
119
|
+
export const charactersAlphaLower = "abcdefghijklmnopqrstuvwxyz";
|
|
120
|
+
export const charactersAlpha = charactersAlphaUpper + charactersAlphaLower;
|
|
121
|
+
export const charactersAlphaNumeric = charactersAlpha + charactersNumeric;
|
|
122
|
+
export const charactersDistinguishable = "CDEHKMPRTUWXY012458";
|
|
123
|
+
|
|
124
|
+
const randomCharactersCache = {
|
|
125
|
+
charactersAlphaNumeric: customAlphabet(charactersAlphaNumeric),
|
|
126
|
+
};
|
|
127
|
+
export const randomCharacters = (
|
|
128
|
+
length,
|
|
129
|
+
characters = charactersAlphaNumeric,
|
|
130
|
+
) => {
|
|
131
|
+
randomCharactersCache[characters] ??= customAlphabet(characters);
|
|
132
|
+
return randomCharactersCache[characters](length);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export const randomAlphaNumeric = (characterLength) => {
|
|
136
|
+
return randomCharacters(characterLength, charactersAlphaNumeric);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export const randomNumeric = (characterLength) => {
|
|
140
|
+
let value = "";
|
|
141
|
+
for (let i = characterLength; i--; ) {
|
|
142
|
+
value += randomInt(9);
|
|
143
|
+
}
|
|
144
|
+
return value;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// *** configs *** //
|
|
148
|
+
// Input: {id, prefix, entropy, characters, opt, expire}
|
|
149
|
+
// Output: {id, type, opt, expire, create, ...}
|
|
150
|
+
export const makeRandomConfigObject = ({
|
|
151
|
+
id,
|
|
152
|
+
prefix = "",
|
|
153
|
+
entropy = 64,
|
|
154
|
+
characters = charactersAlphaNumeric,
|
|
155
|
+
...params
|
|
156
|
+
} = {}) => {
|
|
157
|
+
const minLength = entropyToCharacterLength(entropy, characters.length);
|
|
158
|
+
const config = {
|
|
159
|
+
id,
|
|
160
|
+
type: "id",
|
|
161
|
+
create: () => prefix + randomCharacters(minLength, characters),
|
|
162
|
+
...params,
|
|
163
|
+
};
|
|
164
|
+
return config;
|
|
165
|
+
};
|
|
214
166
|
|
|
215
167
|
// *** Digests *** //
|
|
216
|
-
export const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
168
|
+
export const randomChecksumSalt = () => {
|
|
169
|
+
return randomBytes(32); // 256 bits
|
|
170
|
+
};
|
|
171
|
+
export const randomChecksumPepper = () => {
|
|
172
|
+
return randomIV(); // 96
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
export const createSaltedValue = (value, { checksumSalt } = {}) => {
|
|
176
|
+
checksumSalt ??= options.digestChecksumSalt;
|
|
177
|
+
if (!checksumSalt) {
|
|
178
|
+
return value;
|
|
179
|
+
}
|
|
180
|
+
const newValue = value + checksumSalt;
|
|
181
|
+
return newValue;
|
|
182
|
+
};
|
|
183
|
+
export const createPepperedValue = (
|
|
184
|
+
value,
|
|
185
|
+
{ checksumPepper, encryptionKey } = {},
|
|
186
|
+
) => {
|
|
187
|
+
checksumPepper ??= options.digestChecksumPepper;
|
|
188
|
+
encryptionKey ??= options.symmetricEncryptionKey;
|
|
189
|
+
if (!checksumPepper || !encryptionKey) {
|
|
190
|
+
return value;
|
|
191
|
+
}
|
|
192
|
+
const newValue = symmetricEncrypt(value, {
|
|
193
|
+
encryptionKey,
|
|
194
|
+
sub: "",
|
|
195
|
+
iv: checksumPepper,
|
|
196
|
+
});
|
|
197
|
+
return newValue;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
export const createChecksum = (value, { hashAlgorithm, encoding } = {}) => {
|
|
201
|
+
hashAlgorithm ??= options.digestChecksumHashAlgorithm;
|
|
202
|
+
encoding ??= options.digestChecksumEncoding;
|
|
203
|
+
return createHash(hashAlgorithm).update(value).digest(encoding);
|
|
204
|
+
};
|
|
205
|
+
export const createSeasonedChecksum = (
|
|
206
|
+
value,
|
|
207
|
+
{ hashAlgorithm, encoding, checksumSalt, checksumPepper } = {},
|
|
208
|
+
) => {
|
|
209
|
+
return createChecksum(
|
|
210
|
+
createPepperedValue(createSaltedValue(value, { checksumSalt }), {
|
|
211
|
+
checksumPepper,
|
|
212
|
+
}),
|
|
213
|
+
{
|
|
214
|
+
hashAlgorithm,
|
|
215
|
+
encoding,
|
|
216
|
+
},
|
|
217
|
+
);
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
export const createDigest = (value, { hashAlgorithm, encoding } = {}) => {
|
|
221
|
+
hashAlgorithm ??= options.digestChecksumHashAlgorithm;
|
|
222
|
+
const checksum = createChecksum(value, { hashAlgorithm, encoding });
|
|
223
|
+
return `${hashAlgorithm}:${checksum}`;
|
|
224
|
+
};
|
|
225
|
+
export const createSaltedDigest = (
|
|
226
|
+
value,
|
|
227
|
+
{ hashAlgorithm, encoding, checksumSalt } = {},
|
|
228
|
+
) => {
|
|
229
|
+
hashAlgorithm ??= options.digestChecksumHashAlgorithm;
|
|
230
|
+
const checksum = createChecksum(createSaltedValue(value, { checksumSalt }), {
|
|
231
|
+
hashAlgorithm,
|
|
232
|
+
encoding,
|
|
233
|
+
});
|
|
234
|
+
return `${hashAlgorithm}:${checksum}`;
|
|
235
|
+
};
|
|
236
|
+
export const createPepperedDigest = (
|
|
237
|
+
value,
|
|
238
|
+
{ hashAlgorithm, encoding, checksumPepper, encryptionKey } = {},
|
|
239
|
+
) => {
|
|
240
|
+
hashAlgorithm ??= options.digestChecksumHashAlgorithm;
|
|
241
|
+
const checksum = createChecksum(
|
|
242
|
+
createPepperedValue(value, { checksumPepper, encryptionKey }),
|
|
243
|
+
{
|
|
244
|
+
hashAlgorithm,
|
|
245
|
+
encoding,
|
|
246
|
+
},
|
|
247
|
+
);
|
|
248
|
+
return `${hashAlgorithm}:${checksum}`;
|
|
249
|
+
};
|
|
250
|
+
export const createSeasonedDigest = (
|
|
251
|
+
value,
|
|
252
|
+
{ hashAlgorithm, encoding, checksumSalt, checksumPepper, encryptionKey } = {},
|
|
253
|
+
) => {
|
|
254
|
+
hashAlgorithm ??= options.digestChecksumHashAlgorithm;
|
|
255
|
+
const checksum = createSeasonedChecksum(value, {
|
|
256
|
+
hashAlgorithm,
|
|
257
|
+
encoding,
|
|
258
|
+
checksumSalt,
|
|
259
|
+
checksumPepper,
|
|
260
|
+
encryptionKey,
|
|
261
|
+
});
|
|
262
|
+
return `${hashAlgorithm}:${checksum}`;
|
|
263
|
+
};
|
|
224
264
|
|
|
225
265
|
// *** Hashing *** //
|
|
226
266
|
const hashOptions = {
|
|
227
|
-
timeCost: 3,
|
|
228
|
-
memoryCost: 2 **
|
|
267
|
+
timeCost: 3, // Default 3
|
|
268
|
+
memoryCost: 2 ** 15, // Default 2 ** 12 = 4MB
|
|
229
269
|
saltLength: 16,
|
|
230
|
-
parallelism: 1,
|
|
231
|
-
outputLen: 64, // hashLength: 128
|
|
232
|
-
algorithm: 2,
|
|
233
|
-
version: 1
|
|
234
|
-
}
|
|
270
|
+
parallelism: 1, // Default 1
|
|
271
|
+
outputLen: 64, // hashLength: 128 // Default 32
|
|
272
|
+
algorithm: 2, // Default 2 = Argon2id
|
|
273
|
+
version: 1, // Default 1 = version 19
|
|
274
|
+
};
|
|
235
275
|
|
|
236
276
|
export const createSecretHash = async (value, options = hashOptions) => {
|
|
237
|
-
return secretHash(value, options)
|
|
238
|
-
}
|
|
277
|
+
return secretHash(value, options);
|
|
278
|
+
};
|
|
239
279
|
|
|
240
280
|
export const verifySecretHash = async (hash, value) => {
|
|
241
|
-
return secretVerify(hash, value)
|
|
242
|
-
}
|
|
281
|
+
return secretVerify(hash, value);
|
|
282
|
+
};
|
|
243
283
|
|
|
244
|
-
// *** Encryption *** //
|
|
245
|
-
const authTagLength = 16
|
|
284
|
+
// *** Symmetric Encryption *** //
|
|
285
|
+
const authTagLength = 16;
|
|
246
286
|
|
|
247
|
-
export const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
287
|
+
export const symmetricRandomEncryptionKey = () => {
|
|
288
|
+
return randomBytes(32); // 256 bits
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
export const randomIV = () => {
|
|
292
|
+
return randomBytes(12); // 96 bits
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
export const symmetricGenerateEncryptionKey = (
|
|
296
|
+
sub,
|
|
297
|
+
{ encryptionKey, signatureSecret } = {},
|
|
298
|
+
) => {
|
|
299
|
+
encryptionKey ??= options.symmetricEncryptionKey;
|
|
300
|
+
signatureSecret ??= options.symemeticSignatureSecret;
|
|
301
|
+
|
|
302
|
+
const rowEncryptionKey = symmetricRandomEncryptionKey();
|
|
303
|
+
const rowEncryptedKey = symmetricEncrypt(rowEncryptionKey, {
|
|
253
304
|
encryptionKey,
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
return
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
|
|
305
|
+
signatureSecret,
|
|
306
|
+
sub,
|
|
307
|
+
});
|
|
308
|
+
return { encryptionKey: rowEncryptionKey, encryptedKey: rowEncryptedKey };
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
// sub add context to encryption
|
|
312
|
+
export const symmetricEncryptFields = (
|
|
313
|
+
values,
|
|
314
|
+
{ encryptedKey, encryptionKey, signatureSecret, sub },
|
|
315
|
+
fields = [],
|
|
316
|
+
) => {
|
|
317
|
+
if (encryptedKey) {
|
|
318
|
+
encryptionKey ??= symmetricDecryptKey(encryptedKey, {
|
|
319
|
+
signatureSecret,
|
|
320
|
+
sub,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
if (!encryptionKey) return values;
|
|
324
|
+
const encryptedValues = structuredClone(values);
|
|
325
|
+
for (const key of fields) {
|
|
326
|
+
encryptedValues[key] &&= symmetricEncrypt(encryptedValues[key], {
|
|
327
|
+
encryptionKey,
|
|
328
|
+
signatureSecret,
|
|
329
|
+
sub,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
return encryptedValues;
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
export const symmetricEncrypt = (
|
|
282
336
|
data,
|
|
283
|
-
encryptionKey,
|
|
284
|
-
assocData,
|
|
285
|
-
decoding = 'utf8',
|
|
286
|
-
encoding = 'hex'
|
|
337
|
+
{ encryptedKey, encryptionKey, signatureSecret, sub, decoding, encoding, iv },
|
|
287
338
|
) => {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
339
|
+
if (encryptedKey) {
|
|
340
|
+
encryptionKey ??= symmetricDecryptKey(encryptedKey, {
|
|
341
|
+
signatureSecret,
|
|
342
|
+
sub,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
if (!encryptionKey || !data) return data;
|
|
346
|
+
decoding ??= "utf8";
|
|
347
|
+
encoding ??= options.symmetricEncryptionEncoding;
|
|
348
|
+
iv ??= randomIV();
|
|
349
|
+
const cipher = createCipheriv(
|
|
350
|
+
options.symmetricEncryptionAlgorithm,
|
|
351
|
+
encryptionKey,
|
|
352
|
+
iv,
|
|
353
|
+
{
|
|
354
|
+
authTagLength,
|
|
355
|
+
},
|
|
356
|
+
);
|
|
357
|
+
cipher.setAAD(sub);
|
|
293
358
|
const encryptedData =
|
|
294
|
-
cipher.update(data, decoding, encoding) + cipher.final(encoding)
|
|
295
|
-
const authTag = cipher.getAuthTag()
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
359
|
+
cipher.update(data, decoding, encoding) + cipher.final(encoding);
|
|
360
|
+
const authTag = cipher.getAuthTag();
|
|
361
|
+
|
|
362
|
+
const encryptedDataPacket =
|
|
363
|
+
iv.toString(encoding) + authTag.toString(encoding) + encryptedData;
|
|
364
|
+
|
|
365
|
+
// add signature to end
|
|
366
|
+
return symmetricSignatureSign(encryptedDataPacket, { signatureSecret });
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
export const symmetricDecryptFields = (
|
|
370
|
+
encryptedValues,
|
|
371
|
+
{ encryptedKey, encryptionKey, signatureSecret, sub },
|
|
372
|
+
fields = [],
|
|
373
|
+
) => {
|
|
374
|
+
if (encryptedKey) {
|
|
375
|
+
encryptionKey ??= symmetricDecryptKey(encryptedKey, {
|
|
376
|
+
signatureSecret,
|
|
377
|
+
sub,
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
if (!encryptionKey) return encryptedValues;
|
|
381
|
+
const values = structuredClone(encryptedValues);
|
|
382
|
+
for (const key of fields) {
|
|
383
|
+
values[key] &&= symmetricDecrypt(values[key], {
|
|
384
|
+
encryptionKey,
|
|
385
|
+
signatureSecret,
|
|
386
|
+
sub,
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
return values;
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
export const symmetricDecryptKey = (
|
|
393
|
+
encryptedKey,
|
|
394
|
+
{ sub, encryptionKey, signatureSecret } = {},
|
|
395
|
+
) => {
|
|
396
|
+
encryptionKey ??= options.symmetricEncryptionKey;
|
|
397
|
+
signatureSecret ??= options.symemeticSignatureSecret;
|
|
398
|
+
return Buffer.from(
|
|
399
|
+
symmetricDecrypt(encryptedKey, {
|
|
400
|
+
encryptionKey,
|
|
401
|
+
signatureSecret,
|
|
402
|
+
sub,
|
|
403
|
+
encoding: options.symmetricEncryptionEncoding,
|
|
404
|
+
}),
|
|
405
|
+
options.symmetricEncryptionEncoding,
|
|
406
|
+
);
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
export const symmetricDecrypt = (
|
|
410
|
+
encryptedDataPacket,
|
|
411
|
+
{ encryptedKey, encryptionKey, signatureSecret, sub, decoding, encoding },
|
|
328
412
|
) => {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
413
|
+
if (encryptedKey) {
|
|
414
|
+
encryptionKey ??= symmetricDecryptKey(encryptedKey, {
|
|
415
|
+
signatureSecret,
|
|
416
|
+
sub,
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
if (!encryptionKey || !encryptedDataPacket) return encryptedDataPacket;
|
|
420
|
+
decoding ??= options.symmetricEncryptionEncoding;
|
|
421
|
+
encoding ??= "utf8";
|
|
422
|
+
|
|
423
|
+
// remove signature when successful
|
|
424
|
+
encryptedDataPacket = symmetricSignatureVerify(encryptedDataPacket, {
|
|
425
|
+
signatureSecret,
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
if (encryptedDataPacket === false) {
|
|
429
|
+
throw new Error("Signature incorrect");
|
|
430
|
+
}
|
|
431
|
+
const iv = Buffer.from(
|
|
432
|
+
encryptedDataPacket.substring(0, symmetricEncryptionEncodingLengths.iv),
|
|
433
|
+
decoding,
|
|
434
|
+
);
|
|
435
|
+
const authTag = Buffer.from(
|
|
436
|
+
encryptedDataPacket.substring(
|
|
437
|
+
symmetricEncryptionEncodingLengths.iv,
|
|
438
|
+
symmetricEncryptionEncodingLengths.ivAndAuthTag,
|
|
439
|
+
),
|
|
440
|
+
decoding,
|
|
441
|
+
);
|
|
442
|
+
const encryptedData = Buffer.from(
|
|
443
|
+
encryptedDataPacket.substring(
|
|
444
|
+
symmetricEncryptionEncodingLengths.ivAndAuthTag,
|
|
445
|
+
),
|
|
446
|
+
decoding,
|
|
447
|
+
);
|
|
332
448
|
|
|
333
449
|
const decipher = createDecipheriv(
|
|
334
|
-
options.
|
|
450
|
+
options.symmetricEncryptionAlgorithm,
|
|
335
451
|
encryptionKey,
|
|
336
452
|
iv,
|
|
337
453
|
{
|
|
338
|
-
authTagLength
|
|
339
|
-
}
|
|
340
|
-
)
|
|
341
|
-
decipher.setAAD(
|
|
342
|
-
|
|
343
|
-
|
|
454
|
+
authTagLength,
|
|
455
|
+
},
|
|
456
|
+
);
|
|
457
|
+
decipher.setAAD(sub);
|
|
458
|
+
|
|
459
|
+
decipher.setAuthTag(authTag);
|
|
460
|
+
const data =
|
|
344
461
|
decipher.update(encryptedData, decoding, encoding) +
|
|
345
|
-
decipher.final(encoding)
|
|
346
|
-
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// *** Signatures *** //
|
|
350
|
-
export const
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
462
|
+
decipher.final(encoding);
|
|
463
|
+
return data;
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
// *** Symmetric Signatures *** //
|
|
467
|
+
export const symmetricRandomSignatureSecret = () => {
|
|
468
|
+
return randomBytes(32); // 256 bits
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
export const symmetricGenerateSignatureSecret = () => {
|
|
472
|
+
const signatureSecret = symmetricRandomSignatureSecret();
|
|
473
|
+
return { signatureSecret };
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
export const symmetricSignatureSign = (
|
|
477
|
+
data,
|
|
478
|
+
{ hashAlgorithm, signatureSecret } = {},
|
|
479
|
+
) => {
|
|
480
|
+
signatureSecret ??= options.symmetricSignatureSecret;
|
|
481
|
+
hashAlgorithm ??= options.symmetricSignatureHashAlgorithm;
|
|
482
|
+
const signature = createHmac(hashAlgorithm, signatureSecret)
|
|
483
|
+
.update(data)
|
|
484
|
+
.digest(options.symmetricSignatureEncoding)
|
|
485
|
+
.replace(/=+$/, "");
|
|
486
|
+
|
|
487
|
+
const signedData = data + "." + signature;
|
|
488
|
+
return signedData;
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
export const symmetricSignatureVerify = (
|
|
492
|
+
signedData,
|
|
493
|
+
{ hashAlgorithm, signatureSecret } = {},
|
|
494
|
+
) => {
|
|
495
|
+
if (typeof signedData !== "string") return false;
|
|
496
|
+
let lastIndexOf = signedData.lastIndexOf(".");
|
|
497
|
+
// Test for unsigned
|
|
498
|
+
if (lastIndexOf < 0) {
|
|
499
|
+
lastIndexOf = signedData.length;
|
|
500
|
+
}
|
|
501
|
+
const data = signedData.substring(0, lastIndexOf);
|
|
502
|
+
const signedDataExpected = symmetricSignatureSign(data, {
|
|
503
|
+
hashAlgorithm,
|
|
504
|
+
signatureSecret,
|
|
505
|
+
});
|
|
506
|
+
return safeEqual(signedData, signedDataExpected) && data;
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
// Allow rotation of global encryption key, global signature secret, and row encryption key
|
|
510
|
+
export const symmetricRotation = (
|
|
511
|
+
oldEncryptedValues,
|
|
512
|
+
oldOptions, // { encryptionKey, signatureSecret, sub, decoding, encoding }, // old
|
|
513
|
+
oldFields = [],
|
|
514
|
+
newOptions, // { encryptionKey, signatureSecret, sub, decoding, encoding }, // new
|
|
515
|
+
newFields,
|
|
516
|
+
transform = (data) => {
|
|
517
|
+
return data;
|
|
518
|
+
},
|
|
519
|
+
) => {
|
|
520
|
+
newOptions ??= oldOptions;
|
|
521
|
+
newFields ??= oldFields;
|
|
522
|
+
|
|
523
|
+
if (oldOptions.sub !== newOptions.sub) throw new Error("Mismatching `sub`");
|
|
524
|
+
|
|
525
|
+
oldEncryptedValues = structuredClone(oldEncryptedValues);
|
|
526
|
+
// Don't use structuredClone, converts Buffer to Uint8Array ...
|
|
527
|
+
oldOptions = { ...oldOptions };
|
|
528
|
+
newOptions = { ...newOptions };
|
|
529
|
+
|
|
530
|
+
// decrypt old encryption key
|
|
531
|
+
const { encryptionKey: oldEncryptedKey } = oldEncryptedValues;
|
|
532
|
+
delete oldEncryptedValues.encryptionKey;
|
|
533
|
+
|
|
534
|
+
const oldEncryptionKey = symmetricDecryptKey(oldEncryptedKey, oldOptions);
|
|
535
|
+
oldOptions.encryptionKey = oldEncryptionKey;
|
|
536
|
+
|
|
537
|
+
// decrypt
|
|
538
|
+
const data = transform(
|
|
539
|
+
symmetricDecryptFields(
|
|
540
|
+
oldEncryptedValues,
|
|
541
|
+
{ ...oldOptions, encryptionKey: oldEncryptionKey },
|
|
542
|
+
oldFields,
|
|
543
|
+
),
|
|
544
|
+
);
|
|
545
|
+
|
|
546
|
+
// rotate encryptionKey
|
|
547
|
+
const { encryptionKey: newEncryptionKey, encryptedKey: newEncryptedKey } =
|
|
548
|
+
symmetricGenerateEncryptionKey(newOptions.sub, newOptions);
|
|
549
|
+
newOptions.encryptionKey = newEncryptionKey;
|
|
550
|
+
|
|
551
|
+
// encrypt
|
|
552
|
+
const newEncryptedValues = symmetricEncryptFields(
|
|
553
|
+
data,
|
|
554
|
+
newOptions,
|
|
555
|
+
newFields,
|
|
556
|
+
);
|
|
557
|
+
newEncryptedValues.encryptionKey = newEncryptedKey;
|
|
558
|
+
|
|
559
|
+
return newEncryptedValues;
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
// *** Asymmetric Signatures *** //
|
|
563
|
+
// asymmetricKeyPairType
|
|
564
|
+
export const makeAsymmetricKeys = async () => {
|
|
565
|
+
const { publicKey, privateKey } = await generateKeyPair("ec", {
|
|
566
|
+
namedCurve: options.asymmetricKeyNamedCurve,
|
|
567
|
+
paramEncoding: "named",
|
|
354
568
|
publicKeyEncoding: {
|
|
355
|
-
type:
|
|
356
|
-
format:
|
|
569
|
+
type: "spki",
|
|
570
|
+
format: "pem",
|
|
357
571
|
},
|
|
358
572
|
privateKeyEncoding: {
|
|
359
|
-
type:
|
|
360
|
-
format:
|
|
361
|
-
//
|
|
362
|
-
cipher: options.
|
|
363
|
-
passphrase: encryptionKey
|
|
364
|
-
}
|
|
365
|
-
})
|
|
366
|
-
return { publicKey, privateKey }
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
export const
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
573
|
+
type: "sec1",
|
|
574
|
+
format: "pem",
|
|
575
|
+
// Encryption done at another level for consistency
|
|
576
|
+
// cipher: options.asymmetricEncryptionAlgorithm,
|
|
577
|
+
// passphrase: encryptionKey,
|
|
578
|
+
},
|
|
579
|
+
});
|
|
580
|
+
return { publicKey, privateKey };
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
export const makeAsymmetricSignature = async (
|
|
584
|
+
data,
|
|
585
|
+
privateKey,
|
|
586
|
+
{ hashAlgorithm } = {},
|
|
587
|
+
) => {
|
|
588
|
+
hashAlgorithm ??= options.asymmetricSignatureHashAlgorithm;
|
|
589
|
+
return (await sign(hashAlgorithm, Buffer.from(data), privateKey)).toString(
|
|
590
|
+
options.asymmetricSignatureEncoding,
|
|
591
|
+
);
|
|
592
|
+
};
|
|
593
|
+
export const verifyAsymmetricSignature = async (
|
|
373
594
|
data,
|
|
374
595
|
publicKey,
|
|
375
596
|
signature,
|
|
376
|
-
|
|
597
|
+
{ hashAlgorithm } = {},
|
|
377
598
|
) => {
|
|
378
|
-
|
|
379
|
-
|
|
599
|
+
hashAlgorithm ??= options.asymmetricSignatureHashAlgorithm;
|
|
600
|
+
return await verify(
|
|
601
|
+
hashAlgorithm,
|
|
380
602
|
Buffer.from(data),
|
|
381
603
|
publicKey,
|
|
382
|
-
Buffer.from(signature,
|
|
383
|
-
)
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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 *** //
|
|
604
|
+
Buffer.from(signature, options.asymmetricSignatureEncoding),
|
|
605
|
+
);
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
export const safeEqual = (input, expected) => {
|
|
609
|
+
const bufferInput = Buffer.from(input);
|
|
610
|
+
const bufferExpected = Buffer.from(expected);
|
|
611
|
+
return (
|
|
612
|
+
bufferInput.length === bufferExpected.length &&
|
|
613
|
+
timingSafeEqual(bufferInput, bufferExpected)
|
|
614
|
+
);
|
|
615
|
+
};
|
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.70",
|
|
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": {
|
|
@@ -41,15 +41,16 @@
|
|
|
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": {
|
|
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
|
+
"nanoid": "5.1.5"
|
|
54
55
|
}
|
|
55
56
|
}
|
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.
|