@catbee/utils 2.0.1 → 2.0.3
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/crypto/index.cjs +288 -0
- package/crypto/index.d.ts +230 -3
- package/crypto/index.mjs +283 -2
- package/date/index.cjs +343 -102
- package/date/index.d.ts +178 -6
- package/date/index.mjs +338 -103
- package/logger/index.cjs +1 -1
- package/logger/index.mjs +1 -1
- package/package.json +6 -6
- package/server/index.cjs +175 -11
- package/server/index.d.ts +89 -2
- package/server/index.mjs +177 -13
- package/types/index.d.ts +3 -2
- package/validation/index.cjs +30 -4
- package/validation/index.d.ts +29 -3
- package/validation/index.mjs +30 -5
package/crypto/index.mjs
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* SOFTWARE.
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
-
import { pbkdf2, scrypt, createHmac, createHash, randomBytes, timingSafeEqual, createCipheriv, createDecipheriv } from 'crypto';
|
|
25
|
+
import { pbkdf2, scrypt, webcrypto, createHmac, createHash, randomBytes, timingSafeEqual, createCipheriv, createDecipheriv } from 'crypto';
|
|
26
26
|
import { promisify } from 'util';
|
|
27
27
|
import { uuid } from '@catbee/utils/id';
|
|
28
28
|
|
|
@@ -54,6 +54,287 @@ function md5(input) {
|
|
|
54
54
|
return hash("md5", input);
|
|
55
55
|
}
|
|
56
56
|
__name(md5, "md5");
|
|
57
|
+
var DEFAULT_SIGNATURE_ENCODING = "base64url";
|
|
58
|
+
var subtle = globalThis.crypto?.subtle ?? webcrypto.subtle;
|
|
59
|
+
function getDigestLength(hash2) {
|
|
60
|
+
switch (hash2) {
|
|
61
|
+
case "SHA-256":
|
|
62
|
+
return 32;
|
|
63
|
+
case "SHA-384":
|
|
64
|
+
return 48;
|
|
65
|
+
case "SHA-512":
|
|
66
|
+
return 64;
|
|
67
|
+
default:
|
|
68
|
+
return 32;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
__name(getDigestLength, "getDigestLength");
|
|
72
|
+
function resolveKeyAlgorithm({ type = "RSA-PSS", modulusLength = 2048, hash: hash2 = "SHA-256", namedCurve = "P-256" }) {
|
|
73
|
+
switch (type) {
|
|
74
|
+
case "RSA":
|
|
75
|
+
if (!Number.isInteger(modulusLength) || modulusLength < 2048) {
|
|
76
|
+
throw new Error("RSA modulusLength must be an integer greater than or equal to 2048");
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
name: "RSASSA-PKCS1-v1_5",
|
|
80
|
+
modulusLength,
|
|
81
|
+
publicExponent: new Uint8Array([
|
|
82
|
+
1,
|
|
83
|
+
0,
|
|
84
|
+
1
|
|
85
|
+
]),
|
|
86
|
+
hash: hash2
|
|
87
|
+
};
|
|
88
|
+
case "RSA-PSS":
|
|
89
|
+
if (!Number.isInteger(modulusLength) || modulusLength < 2048) {
|
|
90
|
+
throw new Error("RSA-PSS modulusLength must be an integer greater than or equal to 2048");
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
name: "RSA-PSS",
|
|
94
|
+
modulusLength,
|
|
95
|
+
publicExponent: new Uint8Array([
|
|
96
|
+
1,
|
|
97
|
+
0,
|
|
98
|
+
1
|
|
99
|
+
]),
|
|
100
|
+
hash: hash2
|
|
101
|
+
};
|
|
102
|
+
case "ECDSA":
|
|
103
|
+
return {
|
|
104
|
+
name: "ECDSA",
|
|
105
|
+
namedCurve
|
|
106
|
+
};
|
|
107
|
+
case "Ed25519":
|
|
108
|
+
return {
|
|
109
|
+
name: "Ed25519"
|
|
110
|
+
};
|
|
111
|
+
default:
|
|
112
|
+
throw new Error(`Unsupported key type: ${type}. Supported types: RSA, RSA-PSS, ECDSA, Ed25519`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
__name(resolveKeyAlgorithm, "resolveKeyAlgorithm");
|
|
116
|
+
function toBase64(buffer) {
|
|
117
|
+
if (typeof Buffer !== "undefined") {
|
|
118
|
+
return Buffer.from(buffer).toString("base64");
|
|
119
|
+
}
|
|
120
|
+
let binary = "";
|
|
121
|
+
const bytes = new Uint8Array(buffer);
|
|
122
|
+
const chunkSize = 32768;
|
|
123
|
+
for (let i = 0; i < bytes.length; i += chunkSize) {
|
|
124
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + chunkSize));
|
|
125
|
+
}
|
|
126
|
+
return btoa(binary);
|
|
127
|
+
}
|
|
128
|
+
__name(toBase64, "toBase64");
|
|
129
|
+
function toPEM(base64, type, formatPemLines, addPrefixSuffix) {
|
|
130
|
+
const formattedBase64 = formatPemLines ? base64.match(/.{1,64}/g)?.join("\n") ?? base64 : base64;
|
|
131
|
+
if (!addPrefixSuffix) {
|
|
132
|
+
return formattedBase64;
|
|
133
|
+
}
|
|
134
|
+
const prefix = `-----BEGIN ${type}-----`;
|
|
135
|
+
const suffix = `-----END ${type}-----`;
|
|
136
|
+
return `${prefix}
|
|
137
|
+
${formattedBase64}
|
|
138
|
+
${suffix}`;
|
|
139
|
+
}
|
|
140
|
+
__name(toPEM, "toPEM");
|
|
141
|
+
function fromPEM(pem) {
|
|
142
|
+
const trimmedPem = pem.trim();
|
|
143
|
+
const format = trimmedPem.includes("BEGIN PRIVATE KEY") ? "pkcs8" : trimmedPem.includes("BEGIN PUBLIC KEY") ? "spki" : void 0;
|
|
144
|
+
if (!format) {
|
|
145
|
+
throw new Error("Unsupported PEM format. Expected PUBLIC KEY or PRIVATE KEY PEM block");
|
|
146
|
+
}
|
|
147
|
+
const base64 = trimmedPem.replace(/-----BEGIN [A-Z ]+-----/g, "").replace(/-----END [A-Z ]+-----/g, "").replace(/\s+/g, "");
|
|
148
|
+
return {
|
|
149
|
+
format,
|
|
150
|
+
binary: toArrayBuffer(Buffer.from(base64, "base64"))
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
__name(fromPEM, "fromPEM");
|
|
154
|
+
function toBinary(data, encoding = "utf8") {
|
|
155
|
+
if (typeof data === "string") {
|
|
156
|
+
return Uint8Array.from(Buffer.from(data, encoding));
|
|
157
|
+
}
|
|
158
|
+
if (Buffer.isBuffer(data)) {
|
|
159
|
+
return Uint8Array.from(data);
|
|
160
|
+
}
|
|
161
|
+
return data;
|
|
162
|
+
}
|
|
163
|
+
__name(toBinary, "toBinary");
|
|
164
|
+
function toArrayBuffer(data) {
|
|
165
|
+
const byteView = data instanceof Uint8Array ? data : Uint8Array.from(data);
|
|
166
|
+
const copy = new Uint8Array(byteView.byteLength);
|
|
167
|
+
copy.set(byteView);
|
|
168
|
+
return copy.buffer;
|
|
169
|
+
}
|
|
170
|
+
__name(toArrayBuffer, "toArrayBuffer");
|
|
171
|
+
function inferKeyTypeFromJwk(jwk, fallback) {
|
|
172
|
+
if (fallback) {
|
|
173
|
+
return fallback;
|
|
174
|
+
}
|
|
175
|
+
const keyType = typeof jwk.kty === "string" ? jwk.kty : void 0;
|
|
176
|
+
const curve = typeof jwk.crv === "string" ? jwk.crv : void 0;
|
|
177
|
+
const algorithm = typeof jwk.alg === "string" ? jwk.alg : void 0;
|
|
178
|
+
if (keyType === "OKP" && curve === "Ed25519") {
|
|
179
|
+
return "Ed25519";
|
|
180
|
+
}
|
|
181
|
+
if (keyType === "EC") {
|
|
182
|
+
return "ECDSA";
|
|
183
|
+
}
|
|
184
|
+
if (keyType === "RSA") {
|
|
185
|
+
return algorithm?.startsWith("PS") ? "RSA-PSS" : "RSA";
|
|
186
|
+
}
|
|
187
|
+
throw new Error("Unable to infer key type from JWK. Provide import options with an explicit type");
|
|
188
|
+
}
|
|
189
|
+
__name(inferKeyTypeFromJwk, "inferKeyTypeFromJwk");
|
|
190
|
+
function resolveDefaultUsages(format, usages) {
|
|
191
|
+
return usages ?? (format === "pkcs8" ? [
|
|
192
|
+
"sign"
|
|
193
|
+
] : [
|
|
194
|
+
"verify"
|
|
195
|
+
]);
|
|
196
|
+
}
|
|
197
|
+
__name(resolveDefaultUsages, "resolveDefaultUsages");
|
|
198
|
+
function inferJwkUsages(jwk, usages) {
|
|
199
|
+
if (usages) {
|
|
200
|
+
return usages;
|
|
201
|
+
}
|
|
202
|
+
if (Array.isArray(jwk.key_ops)) {
|
|
203
|
+
const inferredFromKeyOps = jwk.key_ops.filter((usage) => usage === "sign" || usage === "verify");
|
|
204
|
+
if (inferredFromKeyOps.length === 0) {
|
|
205
|
+
throw new Error("JWK key_ops must include at least one of sign or verify when provided");
|
|
206
|
+
}
|
|
207
|
+
return inferredFromKeyOps;
|
|
208
|
+
}
|
|
209
|
+
if (typeof jwk.d === "string" && jwk.d.length > 0) {
|
|
210
|
+
return [
|
|
211
|
+
"sign"
|
|
212
|
+
];
|
|
213
|
+
}
|
|
214
|
+
return [
|
|
215
|
+
"verify"
|
|
216
|
+
];
|
|
217
|
+
}
|
|
218
|
+
__name(inferJwkUsages, "inferJwkUsages");
|
|
219
|
+
function resolveSignAlgorithm(key, options = {}) {
|
|
220
|
+
const hash2 = options.hash ?? "SHA-256";
|
|
221
|
+
switch (key.algorithm.name) {
|
|
222
|
+
case "RSASSA-PKCS1-v1_5":
|
|
223
|
+
return "RSASSA-PKCS1-v1_5";
|
|
224
|
+
case "RSA-PSS": {
|
|
225
|
+
const keyAlgorithm = key.algorithm;
|
|
226
|
+
const pssHash = options.hash ?? keyAlgorithm.hash.name;
|
|
227
|
+
return {
|
|
228
|
+
name: "RSA-PSS",
|
|
229
|
+
saltLength: options.saltLength ?? getDigestLength(pssHash)
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
case "ECDSA":
|
|
233
|
+
return {
|
|
234
|
+
name: "ECDSA",
|
|
235
|
+
hash: hash2
|
|
236
|
+
};
|
|
237
|
+
case "Ed25519":
|
|
238
|
+
return "Ed25519";
|
|
239
|
+
default:
|
|
240
|
+
throw new Error(`Unsupported signing key algorithm: ${key.algorithm.name}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
__name(resolveSignAlgorithm, "resolveSignAlgorithm");
|
|
244
|
+
function arrayBufferToBuffer(buffer) {
|
|
245
|
+
return Buffer.from(buffer);
|
|
246
|
+
}
|
|
247
|
+
__name(arrayBufferToBuffer, "arrayBufferToBuffer");
|
|
248
|
+
async function generateKeys(options = {}) {
|
|
249
|
+
const { type = "RSA-PSS", modulusLength = 2048, hash: hash2 = "SHA-256", namedCurve = "P-256", extractable = false, includeCryptoKeys = false, formatPemLines = true, addPrefixSuffix = true } = options;
|
|
250
|
+
const algorithm = resolveKeyAlgorithm({
|
|
251
|
+
type,
|
|
252
|
+
modulusLength,
|
|
253
|
+
hash: hash2,
|
|
254
|
+
namedCurve
|
|
255
|
+
});
|
|
256
|
+
const usages = [
|
|
257
|
+
"sign",
|
|
258
|
+
"verify"
|
|
259
|
+
];
|
|
260
|
+
const keyPair = await subtle.generateKey(algorithm, extractable, usages);
|
|
261
|
+
const [publicKeyBuf, privateKeyBuf] = await Promise.all([
|
|
262
|
+
subtle.exportKey("spki", keyPair.publicKey),
|
|
263
|
+
extractable ? subtle.exportKey("pkcs8", keyPair.privateKey) : Promise.resolve(void 0)
|
|
264
|
+
]);
|
|
265
|
+
const publicKeyBase64 = toBase64(publicKeyBuf);
|
|
266
|
+
const privateKeyBase64 = privateKeyBuf ? toBase64(privateKeyBuf) : void 0;
|
|
267
|
+
const result = {
|
|
268
|
+
type,
|
|
269
|
+
publicKey: toPEM(publicKeyBase64, "PUBLIC KEY", formatPemLines, addPrefixSuffix),
|
|
270
|
+
publicKeyBuffer: publicKeyBuf
|
|
271
|
+
};
|
|
272
|
+
if (privateKeyBase64 && privateKeyBuf) {
|
|
273
|
+
result.privateKey = toPEM(privateKeyBase64, "PRIVATE KEY", formatPemLines, addPrefixSuffix);
|
|
274
|
+
result.privateKeyBuffer = privateKeyBuf;
|
|
275
|
+
}
|
|
276
|
+
if (includeCryptoKeys) {
|
|
277
|
+
result.privateKeyCrypto = keyPair.privateKey;
|
|
278
|
+
result.publicKeyCrypto = keyPair.publicKey;
|
|
279
|
+
}
|
|
280
|
+
return result;
|
|
281
|
+
}
|
|
282
|
+
__name(generateKeys, "generateKeys");
|
|
283
|
+
async function sign(data, privateKeyCrypto, options = {}) {
|
|
284
|
+
const algorithm = resolveSignAlgorithm(privateKeyCrypto, options);
|
|
285
|
+
const signature = await subtle.sign(algorithm, privateKeyCrypto, toArrayBuffer(toBinary(data, options.inputEncoding)));
|
|
286
|
+
return arrayBufferToBuffer(signature).toString(options.outputEncoding ?? DEFAULT_SIGNATURE_ENCODING);
|
|
287
|
+
}
|
|
288
|
+
__name(sign, "sign");
|
|
289
|
+
async function verify(data, signature, publicKeyCrypto, options = {}) {
|
|
290
|
+
const algorithm = resolveSignAlgorithm(publicKeyCrypto, options);
|
|
291
|
+
const signatureBytes = typeof signature === "string" ? Uint8Array.from(Buffer.from(signature, options.signatureEncoding ?? DEFAULT_SIGNATURE_ENCODING)) : toBinary(signature);
|
|
292
|
+
return subtle.verify(algorithm, publicKeyCrypto, toArrayBuffer(signatureBytes), toArrayBuffer(toBinary(data, options.inputEncoding)));
|
|
293
|
+
}
|
|
294
|
+
__name(verify, "verify");
|
|
295
|
+
async function importKey(key, options = {}) {
|
|
296
|
+
const extractable = options.extractable ?? false;
|
|
297
|
+
if (typeof key === "string") {
|
|
298
|
+
if (!options.type) {
|
|
299
|
+
throw new Error("Key type is required when importing PEM keys");
|
|
300
|
+
}
|
|
301
|
+
const { format, binary } = fromPEM(key);
|
|
302
|
+
const algorithm2 = resolveKeyAlgorithm({
|
|
303
|
+
type: options.type,
|
|
304
|
+
hash: options.hash,
|
|
305
|
+
namedCurve: options.namedCurve
|
|
306
|
+
});
|
|
307
|
+
return subtle.importKey(format, binary, algorithm2, extractable, resolveDefaultUsages(format, options.usages));
|
|
308
|
+
}
|
|
309
|
+
const keyType = inferKeyTypeFromJwk(key, options.type);
|
|
310
|
+
const algorithm = resolveKeyAlgorithm({
|
|
311
|
+
type: keyType,
|
|
312
|
+
hash: options.hash,
|
|
313
|
+
namedCurve: options.namedCurve
|
|
314
|
+
});
|
|
315
|
+
return subtle.importKey("jwk", key, algorithm, extractable, inferJwkUsages(key, options.usages));
|
|
316
|
+
}
|
|
317
|
+
__name(importKey, "importKey");
|
|
318
|
+
async function exportKey(key, format = "jwk", options = {}) {
|
|
319
|
+
if (format === "jwk") {
|
|
320
|
+
return subtle.exportKey("jwk", key);
|
|
321
|
+
}
|
|
322
|
+
const binary = key.type === "private" ? await subtle.exportKey("pkcs8", key) : await subtle.exportKey("spki", key);
|
|
323
|
+
return toPEM(toBase64(binary), key.type === "private" ? "PRIVATE KEY" : "PUBLIC KEY", options.formatPemLines ?? true, options.addPrefixSuffix ?? true);
|
|
324
|
+
}
|
|
325
|
+
__name(exportKey, "exportKey");
|
|
326
|
+
async function fingerprint(publicKey, encoding = "base64url") {
|
|
327
|
+
if (publicKey.type !== "public") {
|
|
328
|
+
throw new Error("fingerprint requires a public key");
|
|
329
|
+
}
|
|
330
|
+
const spki = await subtle.exportKey("spki", publicKey);
|
|
331
|
+
return createHash("sha256").update(arrayBufferToBuffer(spki)).digest(encoding);
|
|
332
|
+
}
|
|
333
|
+
__name(fingerprint, "fingerprint");
|
|
334
|
+
async function getKeyId(publicKey) {
|
|
335
|
+
return fingerprint(publicKey, "base64url");
|
|
336
|
+
}
|
|
337
|
+
__name(getKeyId, "getKeyId");
|
|
57
338
|
function randomString() {
|
|
58
339
|
return sha256(uuid());
|
|
59
340
|
}
|
|
@@ -208,4 +489,4 @@ async function verifyPassword(password, hash2) {
|
|
|
208
489
|
}
|
|
209
490
|
__name(verifyPassword, "verifyPassword");
|
|
210
491
|
|
|
211
|
-
export { createSignedToken, decrypt, encrypt, generateApiKey, generateNonce, generateRandomBytes, generateRandomBytesAsString, hash, hashPassword, hmac, md5, pbkdf2Hash, randomString, safeCompare, secureRandomInt, sha1, sha256, sha256Hmac, verifyPassword, verifySignedToken };
|
|
492
|
+
export { createSignedToken, decrypt, encrypt, exportKey, fingerprint, generateApiKey, generateKeys, generateNonce, generateRandomBytes, generateRandomBytesAsString, getKeyId, hash, hashPassword, hmac, importKey, md5, pbkdf2Hash, randomString, safeCompare, secureRandomInt, sha1, sha256, sha256Hmac, sign, verify, verifyPassword, verifySignedToken };
|