key-rotation-manager 1.0.11 → 1.1.2
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/README.md +78 -8
- package/dist/index.cjs +133 -123
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +133 -122
- package/dist/index.js.map +1 -1
- package/package.json +18 -7
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
A production-ready library for generating, storing, rotating, and retrieving cryptographic keys with support for expiration, versioning, merge strategies, custom storage logic, and lifecycle hooks.
|
|
6
6
|
|
|
7
|
+
**Key Rotation Manager** is a comprehensive Node.js library for secure cryptographic key management. It provides automatic key rotation, expiration handling, versioning, and flexible storage options. Perfect for API key management, token rotation, secret management, and credential handling in production applications.
|
|
8
|
+
|
|
7
9
|
[](https://www.npmjs.com/package/key-rotation-manager)
|
|
8
10
|
[](https://nodejs.org/)
|
|
9
11
|
[](LICENSE)
|
|
@@ -44,6 +46,8 @@ A production-ready library for generating, storing, rotating, and retrieving cry
|
|
|
44
46
|
- 📡 **Event-Driven** - Lifecycle hooks for key events
|
|
45
47
|
- 📁 **Git Integration** - Automatic `.gitignore` management
|
|
46
48
|
- 🎯 **TypeScript** - Full TypeScript support with comprehensive types
|
|
49
|
+
- ⚡ **Zero Dependencies** - Uses native Node.js crypto module
|
|
50
|
+
- 🚀 **Production Ready** - Battle-tested for enterprise applications
|
|
47
51
|
|
|
48
52
|
---
|
|
49
53
|
|
|
@@ -55,6 +59,8 @@ npm install key-rotation-manager
|
|
|
55
59
|
|
|
56
60
|
**Requirements:** Node.js >= 18.0.0
|
|
57
61
|
|
|
62
|
+
**Package:** [npmjs.com/package/key-rotation-manager](https://www.npmjs.com/package/key-rotation-manager)
|
|
63
|
+
|
|
58
64
|
---
|
|
59
65
|
|
|
60
66
|
## 🚀 Quick Start
|
|
@@ -562,6 +568,7 @@ Retrieves a key by path and version.
|
|
|
562
568
|
- `options`: `TGetKeyOptions`
|
|
563
569
|
- `path`: `string` - Storage path
|
|
564
570
|
- `version`: `string` - Key version
|
|
571
|
+
- `disableHooks?`: `boolean` - Skip hook execution (default: `false`)
|
|
565
572
|
- `onRotate?`: Rotation options (if key is expired and rotatable)
|
|
566
573
|
|
|
567
574
|
**Returns:** `Promise<TGetKey>`
|
|
@@ -577,8 +584,45 @@ const result = await keyManager.getKey({
|
|
|
577
584
|
rotate: true,
|
|
578
585
|
},
|
|
579
586
|
});
|
|
587
|
+
|
|
588
|
+
// With disabled hooks for performance
|
|
589
|
+
const result = await keyManager.getKey({
|
|
590
|
+
path: '/keys/api',
|
|
591
|
+
version: 'v1',
|
|
592
|
+
disableHooks: true,
|
|
593
|
+
});
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
### `verifyKey(hashedKey, path, version, disableGetKeyHooks?)`
|
|
597
|
+
|
|
598
|
+
Verifies a hashed key against a stored key.
|
|
599
|
+
|
|
600
|
+
**Parameters:**
|
|
601
|
+
- `hashedKey`: `string` - The hashed key to verify
|
|
602
|
+
- `path`: `string` - Storage path
|
|
603
|
+
- `version`: `string | number` - Key version
|
|
604
|
+
- `disableGetKeyHooks?`: `boolean` - Disable hooks in getKey call (default: `true`)
|
|
605
|
+
|
|
606
|
+
**Returns:** `Promise<boolean>`
|
|
607
|
+
|
|
608
|
+
**Example:**
|
|
609
|
+
```typescript
|
|
610
|
+
const isValid = await keyManager.verifyKey(hashedKey, '/keys/api', 'v1');
|
|
611
|
+
if (isValid) {
|
|
612
|
+
console.log('Key is valid!');
|
|
613
|
+
} else {
|
|
614
|
+
console.log('Key is invalid!');
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// Verification with hooks enabled
|
|
618
|
+
const isValid = await keyManager.verifyKey(hashedKey, '/keys/api', 'v1', false);
|
|
580
619
|
```
|
|
581
620
|
|
|
621
|
+
**Notes:**
|
|
622
|
+
- Uses PBKDF2 verification if the key has a `secret` field
|
|
623
|
+
- Falls back to direct hash comparison otherwise
|
|
624
|
+
- Verifies against expired keys if no ready key is available
|
|
625
|
+
|
|
582
626
|
### `useHooks(hooks)`
|
|
583
627
|
|
|
584
628
|
Sets multiple hooks at once.
|
|
@@ -602,12 +646,16 @@ Sets a single hook.
|
|
|
602
646
|
|
|
603
647
|
## 🎯 Use Cases
|
|
604
648
|
|
|
605
|
-
- **API Key Management** - Secure storage and rotation of API keys
|
|
606
|
-
- **Token Rotation** - Automatic credential rotation for
|
|
607
|
-
- **Secret Management** - File-based secret storage with expiration
|
|
608
|
-
- **Multi-Version Keys** - Support for multiple key versions simultaneously
|
|
609
|
-
- **Custom Persistence** - Integrate with databases or
|
|
610
|
-
- **Backend Services** - Production-ready key management for Node.js applications
|
|
649
|
+
- **API Key Management** - Secure storage and rotation of API keys for third-party services
|
|
650
|
+
- **Token Rotation** - Automatic credential rotation for OAuth tokens, JWT secrets, and access tokens
|
|
651
|
+
- **Secret Management** - File-based secret storage with expiration for application secrets
|
|
652
|
+
- **Multi-Version Keys** - Support for multiple key versions simultaneously during rotation periods
|
|
653
|
+
- **Custom Persistence** - Integrate with databases, cloud storage, or key management services
|
|
654
|
+
- **Backend Services** - Production-ready key management for Node.js applications and microservices
|
|
655
|
+
- **Encryption Keys** - Manage encryption keys for data protection with automatic rotation
|
|
656
|
+
- **Session Keys** - Rotate session encryption keys periodically for enhanced security
|
|
657
|
+
- **Database Credentials** - Rotate database passwords and connection strings securely
|
|
658
|
+
- **Service-to-Service Authentication** - Manage keys for inter-service communication
|
|
611
659
|
|
|
612
660
|
---
|
|
613
661
|
|
|
@@ -615,7 +663,9 @@ Sets a single hook.
|
|
|
615
663
|
|
|
616
664
|
See the full changelog for each version:
|
|
617
665
|
|
|
618
|
-
- **[v1.
|
|
666
|
+
- **[v1.1.2](https://github.com/DucAnh2611/key-rotation-manager/tree/master/changelogs/1.1.2.md)** - Key verification, hook control, enhanced documentation
|
|
667
|
+
- **[v1.1.1](https://github.com/DucAnh2611/key-rotation-manager/tree/master/changelogs/1.1.1.md)** - Native crypto, zero dependencies, bug fixes
|
|
668
|
+
- **[v1.0.10](https://github.com/DucAnh2611/key-rotation-manager/tree/master/changelogs/1.0.10.md)** - Hooks system, enhanced gitIgnore configuration
|
|
619
669
|
|
|
620
670
|
---
|
|
621
671
|
|
|
@@ -648,7 +698,27 @@ Contributions are welcome! Please feel free to submit a Pull Request.
|
|
|
648
698
|
|
|
649
699
|
## 📄 License
|
|
650
700
|
|
|
651
|
-
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
701
|
+
This project is licensed under the MIT License - see the [LICENSE](https://github.com/DucAnh2611/key-rotation-manager/blob/master/LICENSE) file for details.
|
|
702
|
+
|
|
703
|
+
---
|
|
704
|
+
|
|
705
|
+
## 🔍 Search Keywords
|
|
706
|
+
|
|
707
|
+
This package is optimized for searches related to:
|
|
708
|
+
- key rotation
|
|
709
|
+
- key management
|
|
710
|
+
- cryptographic key storage
|
|
711
|
+
- API key rotation
|
|
712
|
+
- secret rotation
|
|
713
|
+
- credential management
|
|
714
|
+
- token rotation
|
|
715
|
+
- key versioning
|
|
716
|
+
- automatic key rotation
|
|
717
|
+
- secure key storage
|
|
718
|
+
- Node.js key management
|
|
719
|
+
- TypeScript key management
|
|
720
|
+
- production key rotation
|
|
721
|
+
- enterprise key management
|
|
652
722
|
|
|
653
723
|
---
|
|
654
724
|
|
package/dist/index.cjs
CHANGED
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
var promises = require('fs/promises');
|
|
4
4
|
var path = require('path');
|
|
5
|
-
var
|
|
5
|
+
var crypto = require('crypto');
|
|
6
6
|
var EventEmitter = require('events');
|
|
7
7
|
|
|
8
8
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
9
|
|
|
10
|
-
var CryptoJS__default = /*#__PURE__*/_interopDefault(CryptoJS);
|
|
11
10
|
var EventEmitter__default = /*#__PURE__*/_interopDefault(EventEmitter);
|
|
12
11
|
|
|
13
12
|
// src/constants/default.constant.ts
|
|
@@ -185,10 +184,13 @@ var isType = (data) => {
|
|
|
185
184
|
return {
|
|
186
185
|
number: typeof data === "number" && !Number.isNaN(Number(data)),
|
|
187
186
|
string: typeof data === "string",
|
|
188
|
-
stringNumber: typeof data === "string"
|
|
187
|
+
stringNumber: typeof data === "string" || typeof data === "number",
|
|
189
188
|
boolean: typeof data === "boolean",
|
|
190
189
|
null: data === null,
|
|
191
|
-
undefined: data === void 0
|
|
190
|
+
undefined: data === void 0,
|
|
191
|
+
optionalNumber: typeof data === "undefined" || typeof data === "number" && !Number.isNaN(Number(data)),
|
|
192
|
+
optionalString: typeof data === "undefined" || typeof data === "string",
|
|
193
|
+
optionalStringNumber: typeof data === "undefined" || typeof data === "string" || typeof data === "number"
|
|
192
194
|
};
|
|
193
195
|
};
|
|
194
196
|
var CryptoService = class {
|
|
@@ -196,97 +198,77 @@ var CryptoService = class {
|
|
|
196
198
|
constructor(options = {}) {
|
|
197
199
|
this.options = { ...DEFAULT_CRYPTO_OPTIONS, ...options };
|
|
198
200
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
201
|
+
/**
|
|
202
|
+
* Generate random bytes and encode them
|
|
203
|
+
*/
|
|
202
204
|
generateRandom(length = 32) {
|
|
203
|
-
const
|
|
204
|
-
return this.
|
|
205
|
+
const buffer = crypto.randomBytes(length);
|
|
206
|
+
return this.encodeBuffer(buffer);
|
|
205
207
|
}
|
|
206
|
-
|
|
208
|
+
/**
|
|
207
209
|
* Generate a random key
|
|
208
|
-
* @param length Length of the key
|
|
210
|
+
* @param length Length of the key in bytes
|
|
209
211
|
* @default cryptoOptions.keyLength
|
|
210
|
-
* @returns { key: string, length: number }
|
|
211
212
|
*/
|
|
212
213
|
generateKey(length = this.options.keyLength) {
|
|
213
214
|
return { key: this.generateRandom(length), length };
|
|
214
215
|
}
|
|
215
|
-
|
|
216
|
+
/**
|
|
216
217
|
* Generate a random salt
|
|
217
|
-
* @param length Length of the salt
|
|
218
|
+
* @param length Length of the salt in bytes
|
|
218
219
|
* @default cryptoOptions.saltLength
|
|
219
|
-
* @returns { salt: string, length: number }
|
|
220
220
|
*/
|
|
221
221
|
generateSalt(length = this.options.saltLength) {
|
|
222
222
|
return { salt: this.generateRandom(length), length };
|
|
223
223
|
}
|
|
224
|
-
|
|
224
|
+
/**
|
|
225
225
|
* Generate a random IV
|
|
226
|
-
* @param length Length of the IV
|
|
226
|
+
* @param length Length of the IV in bytes
|
|
227
227
|
* @default cryptoOptions.ivLength
|
|
228
|
-
* @returns { iv: string, length: number }
|
|
229
228
|
*/
|
|
230
229
|
generateIV(length = this.options.ivLength) {
|
|
231
230
|
return { iv: this.generateRandom(length), length };
|
|
232
231
|
}
|
|
233
|
-
|
|
232
|
+
encodeBuffer(buffer) {
|
|
234
233
|
switch (this.options.encoding) {
|
|
235
234
|
case "hex":
|
|
236
|
-
return
|
|
235
|
+
return buffer.toString("hex");
|
|
237
236
|
case "base64url":
|
|
238
|
-
return
|
|
237
|
+
return buffer.toString("base64url");
|
|
239
238
|
case "base64":
|
|
240
239
|
default:
|
|
241
|
-
return
|
|
240
|
+
return buffer.toString("base64");
|
|
242
241
|
}
|
|
243
242
|
}
|
|
244
|
-
|
|
243
|
+
decodeToBuffer(encoded) {
|
|
245
244
|
switch (this.options.encoding) {
|
|
246
245
|
case "hex":
|
|
247
|
-
return
|
|
246
|
+
return Buffer.from(encoded, "hex");
|
|
248
247
|
case "base64url":
|
|
249
|
-
|
|
250
|
-
const padding = "=".repeat((4 - base64.length % 4) % 4);
|
|
251
|
-
return CryptoJS__default.default.enc.Base64.parse(base64 + padding);
|
|
248
|
+
return Buffer.from(encoded, "base64url");
|
|
252
249
|
case "base64":
|
|
253
250
|
default:
|
|
254
|
-
return
|
|
251
|
+
return Buffer.from(encoded, "base64");
|
|
255
252
|
}
|
|
256
253
|
}
|
|
257
254
|
deriveKey(password, salt, keyLength) {
|
|
258
255
|
if (this.options.kdf === "none") {
|
|
259
|
-
return
|
|
256
|
+
return Buffer.from(password, "hex");
|
|
260
257
|
}
|
|
261
258
|
const actualKeyLength = keyLength ?? this.options.keyLength;
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
getHasher() {
|
|
270
|
-
switch (this.options.hashAlgorithm) {
|
|
271
|
-
case "sha512":
|
|
272
|
-
return CryptoJS__default.default.algo.SHA512;
|
|
273
|
-
case "sha384":
|
|
274
|
-
return CryptoJS__default.default.algo.SHA384;
|
|
275
|
-
case "sha256":
|
|
276
|
-
default:
|
|
277
|
-
return CryptoJS__default.default.algo.SHA256;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
getAESMode() {
|
|
281
|
-
return CryptoJS__default.default.mode.CBC;
|
|
282
|
-
}
|
|
283
|
-
getPadding() {
|
|
284
|
-
return CryptoJS__default.default.pad.Pkcs7;
|
|
259
|
+
return crypto.pbkdf2Sync(
|
|
260
|
+
password,
|
|
261
|
+
salt,
|
|
262
|
+
this.options.iterations,
|
|
263
|
+
actualKeyLength,
|
|
264
|
+
this.options.hashAlgorithm
|
|
265
|
+
);
|
|
285
266
|
}
|
|
286
267
|
/**
|
|
287
|
-
* Encrypt data
|
|
268
|
+
* Encrypt data using AES-CBC
|
|
288
269
|
* @param plainText Text to encrypt
|
|
289
|
-
* @param secret Secret key for encryption
|
|
270
|
+
* @param secret Secret key for encryption
|
|
271
|
+
* @param options Optional lengths for key, salt, and IV
|
|
290
272
|
*/
|
|
291
273
|
encrypt(plainText, secret, {
|
|
292
274
|
keyLength,
|
|
@@ -296,61 +278,48 @@ var CryptoService = class {
|
|
|
296
278
|
keyLength = keyLength ?? this.options.keyLength;
|
|
297
279
|
saltLength = saltLength ?? this.options.saltLength;
|
|
298
280
|
ivLength = ivLength ?? this.options.ivLength;
|
|
299
|
-
const salt = this.options.kdf !== "none" ?
|
|
300
|
-
const iv =
|
|
281
|
+
const salt = this.options.kdf !== "none" ? crypto.randomBytes(saltLength) : Buffer.alloc(0);
|
|
282
|
+
const iv = crypto.randomBytes(ivLength);
|
|
301
283
|
const key = this.deriveKey(secret, salt, keyLength);
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
const ivHex = iv.toString(CryptoJS__default.default.enc.Hex);
|
|
313
|
-
const encryptedHex = encrypted.ciphertext.toString(CryptoJS__default.default.enc.Hex);
|
|
314
|
-
const combined = keyLengthHex + saltLengthHex + ivLengthHex + saltHex + ivHex + encryptedHex;
|
|
315
|
-
return this.encode(CryptoJS__default.default.enc.Hex.parse(combined));
|
|
284
|
+
const cipher = crypto.createCipheriv(this.options.algorithm, key, iv);
|
|
285
|
+
const encrypted = Buffer.concat([cipher.update(plainText, "utf8"), cipher.final()]);
|
|
286
|
+
const keyLengthBuf = Buffer.alloc(2);
|
|
287
|
+
keyLengthBuf.writeUInt16BE(keyLength);
|
|
288
|
+
const saltLengthBuf = Buffer.alloc(2);
|
|
289
|
+
saltLengthBuf.writeUInt16BE(salt.length);
|
|
290
|
+
const ivLengthBuf = Buffer.alloc(2);
|
|
291
|
+
ivLengthBuf.writeUInt16BE(ivLength);
|
|
292
|
+
const combined = Buffer.concat([keyLengthBuf, saltLengthBuf, ivLengthBuf, salt, iv, encrypted]);
|
|
293
|
+
return this.encodeBuffer(combined);
|
|
316
294
|
}
|
|
317
295
|
/**
|
|
318
296
|
* Decrypt data
|
|
319
297
|
* @param encryptedData Encrypted data to decrypt
|
|
320
298
|
* @param secret Secret key for decryption
|
|
299
|
+
* @returns Decrypted string, or empty string if decryption fails (wrong password/corrupted data)
|
|
321
300
|
*/
|
|
322
301
|
decrypt(encryptedData, secret) {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
const encrypted = CryptoJS__default.default.lib.CipherParams.create({
|
|
345
|
-
ciphertext: CryptoJS__default.default.enc.Hex.parse(encryptedHex)
|
|
346
|
-
});
|
|
347
|
-
const key = this.deriveKey(secret, salt, keyLength);
|
|
348
|
-
const decrypted = CryptoJS__default.default.AES.decrypt(encrypted, key, {
|
|
349
|
-
iv,
|
|
350
|
-
mode: this.getAESMode(),
|
|
351
|
-
padding: this.getPadding()
|
|
352
|
-
});
|
|
353
|
-
return decrypted.toString(CryptoJS__default.default.enc.Utf8);
|
|
302
|
+
try {
|
|
303
|
+
const combined = this.decodeToBuffer(encryptedData);
|
|
304
|
+
let offset = 0;
|
|
305
|
+
const keyLength = combined.readUInt16BE(offset);
|
|
306
|
+
offset += 2;
|
|
307
|
+
const saltLength = combined.readUInt16BE(offset);
|
|
308
|
+
offset += 2;
|
|
309
|
+
const ivLength = combined.readUInt16BE(offset);
|
|
310
|
+
offset += 2;
|
|
311
|
+
const salt = combined.subarray(offset, offset + saltLength);
|
|
312
|
+
offset += saltLength;
|
|
313
|
+
const iv = combined.subarray(offset, offset + ivLength);
|
|
314
|
+
offset += ivLength;
|
|
315
|
+
const encrypted = combined.subarray(offset);
|
|
316
|
+
const key = this.deriveKey(secret, salt, keyLength);
|
|
317
|
+
const decipher = crypto.createDecipheriv(this.options.algorithm, key, iv);
|
|
318
|
+
const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
319
|
+
return decrypted.toString("utf8");
|
|
320
|
+
} catch {
|
|
321
|
+
return "";
|
|
322
|
+
}
|
|
354
323
|
}
|
|
355
324
|
/**
|
|
356
325
|
* Hash data (one-way)
|
|
@@ -359,19 +328,17 @@ var CryptoService = class {
|
|
|
359
328
|
* @param salt Optional encoded salt string for deterministic hashing
|
|
360
329
|
*/
|
|
361
330
|
hash(data, secret, salt) {
|
|
362
|
-
|
|
363
|
-
const hasher = this.getHasher();
|
|
364
|
-
let saltWordArray;
|
|
331
|
+
let saltBuffer;
|
|
365
332
|
let saltStr;
|
|
366
333
|
if (salt) {
|
|
367
|
-
|
|
334
|
+
saltBuffer = this.decodeToBuffer(salt);
|
|
368
335
|
saltStr = salt;
|
|
369
336
|
} else {
|
|
370
|
-
|
|
371
|
-
saltStr = this.
|
|
337
|
+
saltBuffer = crypto.randomBytes(this.options.saltLength);
|
|
338
|
+
saltStr = this.encodeBuffer(saltBuffer);
|
|
372
339
|
}
|
|
373
|
-
const hash =
|
|
374
|
-
const hashStr = this.
|
|
340
|
+
const hash = crypto.createHash(this.options.hashAlgorithm).update(data).update(secret).update(saltBuffer).digest();
|
|
341
|
+
const hashStr = this.encodeBuffer(hash);
|
|
375
342
|
return `${saltStr}:${hashStr}`;
|
|
376
343
|
}
|
|
377
344
|
/**
|
|
@@ -385,11 +352,9 @@ var CryptoService = class {
|
|
|
385
352
|
if (!saltStr || !expectedHashStr) {
|
|
386
353
|
return false;
|
|
387
354
|
}
|
|
388
|
-
const
|
|
389
|
-
const
|
|
390
|
-
const
|
|
391
|
-
const hash = hasher.create().update(data).update(secretWordArray).update(salt).finalize();
|
|
392
|
-
const hashStr = this.encode(hash);
|
|
355
|
+
const saltBuffer = this.decodeToBuffer(saltStr);
|
|
356
|
+
const hash = crypto.createHash(this.options.hashAlgorithm).update(data).update(secret).update(saltBuffer).digest();
|
|
357
|
+
const hashStr = this.encodeBuffer(hash);
|
|
393
358
|
return hashStr === expectedHashStr;
|
|
394
359
|
}
|
|
395
360
|
};
|
|
@@ -676,33 +641,33 @@ var KeyManager = class extends Store {
|
|
|
676
641
|
const { path, version } = options;
|
|
677
642
|
const key = await this.getKeyByStore(path, String(version));
|
|
678
643
|
if (!key) {
|
|
679
|
-
this.runKeyHook("onKeyNotFound", path, version);
|
|
644
|
+
if (!options.disableHooks) this.runKeyHook("onKeyNotFound", path, version);
|
|
680
645
|
this.sysLog(`Key not found!`, { path, version });
|
|
681
646
|
return { expired: null, ready: null };
|
|
682
647
|
}
|
|
683
648
|
const { ok, message, isExpired, isRenewable, errorOn } = this.validateKey(key);
|
|
684
649
|
if (!ok && isExpired && isRenewable && key) {
|
|
685
650
|
if (!options.onRotate) {
|
|
686
|
-
this.runKeyHook("onKeyMissingRotateOption", key, options);
|
|
651
|
+
if (!options.disableHooks) this.runKeyHook("onKeyMissingRotateOption", key, options);
|
|
687
652
|
this.sysLog(`Key missing rotate option!`, { path, version });
|
|
688
|
-
return { expired:
|
|
653
|
+
return { expired: key, ready: null };
|
|
689
654
|
}
|
|
690
655
|
const renew = await this.newKey({
|
|
691
656
|
type: key.type,
|
|
692
657
|
...options.onRotate
|
|
693
658
|
});
|
|
694
659
|
const resGetKey = { expired: key, ready: renew.key };
|
|
695
|
-
this.runKeyHook("onKeyRenewed", resGetKey, options);
|
|
660
|
+
if (!options.disableHooks) this.runKeyHook("onKeyRenewed", resGetKey, options);
|
|
696
661
|
this.sysLog(`Key renewed!`, { path, version });
|
|
697
662
|
return resGetKey;
|
|
698
663
|
}
|
|
699
664
|
if (!ok && isExpired && !isRenewable && key) {
|
|
700
|
-
this.runKeyHook("onKeyExpired", path, key);
|
|
665
|
+
if (!options.disableHooks) this.runKeyHook("onKeyExpired", path, key);
|
|
701
666
|
this.sysLog(`Key expired!`, { path, version });
|
|
702
667
|
return { expired: key, ready: null };
|
|
703
668
|
}
|
|
704
669
|
if (!ok) {
|
|
705
|
-
this.runKeyHook("onKeyInvalid", key, message, errorOn);
|
|
670
|
+
if (!options.disableHooks) this.runKeyHook("onKeyInvalid", key, message, errorOn);
|
|
706
671
|
this.sysLog(`Key invalid!`, { path, version });
|
|
707
672
|
return { expired: null, ready: null };
|
|
708
673
|
}
|
|
@@ -748,15 +713,16 @@ var KeyManager = class extends Store {
|
|
|
748
713
|
async newKey(options, variables = {}) {
|
|
749
714
|
const { rotate, duration, type, unit, merge, keyLength } = options;
|
|
750
715
|
const { key, length: kLength } = this.cryptoService.generateKey(keyLength);
|
|
751
|
-
const { salt } = this.cryptoService.generateSalt();
|
|
716
|
+
const { salt: secret } = this.cryptoService.generateSalt();
|
|
752
717
|
this.sysLog(`Key generated
|
|
753
718
|
Options:`, options);
|
|
754
|
-
const hashedKey = this.cryptoService.hash(key,
|
|
719
|
+
const hashedKey = this.cryptoService.hash(key, secret);
|
|
755
720
|
const now = /* @__PURE__ */ new Date();
|
|
756
721
|
const keyGenerated = {
|
|
757
722
|
from: now.toISOString(),
|
|
758
723
|
to: duration && unit ? addDuration(now, duration, unit).toISOString() : "NON_EXPIRED",
|
|
759
724
|
key,
|
|
725
|
+
secret,
|
|
760
726
|
hashed: hashedKey,
|
|
761
727
|
hashedBytes: kLength,
|
|
762
728
|
type,
|
|
@@ -771,6 +737,49 @@ Options:`, options);
|
|
|
771
737
|
});
|
|
772
738
|
return { key: keyGenerated, path };
|
|
773
739
|
}
|
|
740
|
+
/**
|
|
741
|
+
* Verify a key by hashed key and path and version
|
|
742
|
+
* @param hashedKey Hashed key to verify
|
|
743
|
+
* @param path Path to the key
|
|
744
|
+
* @param version Version of the key
|
|
745
|
+
* @param disableGetKeyHooks Disable getKey method hooks, should not use hooks in this method
|
|
746
|
+
* @returns True if the key is valid, false otherwise
|
|
747
|
+
*
|
|
748
|
+
* @example
|
|
749
|
+
* ```ts
|
|
750
|
+
* const isValid = await keyManager.verifyKey(hashedKey, path, version);
|
|
751
|
+
* if (isValid) {
|
|
752
|
+
* console.log('Key is valid!');
|
|
753
|
+
* } else {
|
|
754
|
+
* console.log('Key is invalid!');
|
|
755
|
+
* }
|
|
756
|
+
* ```
|
|
757
|
+
*
|
|
758
|
+
* @note
|
|
759
|
+
* 1. This use getKey method to get key, so disableGetKeyHooks option is set to true, if disableGetKeyHooks is not provided, it will be set to true
|
|
760
|
+
*
|
|
761
|
+
* 2. Get key from store
|
|
762
|
+
* - If the key is expired, the expired key will be used to verify the key
|
|
763
|
+
* - If the key is not expired, the ready key will be used to verify the key
|
|
764
|
+
*
|
|
765
|
+
* 3. If the key is not found, the function will return false
|
|
766
|
+
*
|
|
767
|
+
* 4. Verify key strategy
|
|
768
|
+
* - If the key is found and secret is provided, compare original key with hashed key using secret
|
|
769
|
+
* - If the key is found and secret is not provided, compare original key with hashed key
|
|
770
|
+
*
|
|
771
|
+
*/
|
|
772
|
+
async verifyKey(hashedKey, path, version, disableGetKeyHooks = true) {
|
|
773
|
+
const { ready, expired } = await this.getKey({ path, version: String(version), disableHooks: disableGetKeyHooks });
|
|
774
|
+
if (!ready && !expired) {
|
|
775
|
+
this.sysLog(`Key not found to verify!`, { path, version });
|
|
776
|
+
return false;
|
|
777
|
+
}
|
|
778
|
+
const key = expired ?? ready;
|
|
779
|
+
this.sysLog("Verifying key...", { hashedKey, path, version, validateMethod: key.secret ? "verifyHash(key, hashedKey, secret)" : "hashedKey === key.hashed" });
|
|
780
|
+
if (key.secret) return this.cryptoService.verifyHash(key.key, hashedKey, key.secret);
|
|
781
|
+
return hashedKey === key.hashed;
|
|
782
|
+
}
|
|
774
783
|
async getKeyByStore(path, version) {
|
|
775
784
|
if (this.getKeyFn) {
|
|
776
785
|
return this.getKeyFn(path, version);
|
|
@@ -795,7 +804,8 @@ Options:`, options);
|
|
|
795
804
|
rotate: "boolean",
|
|
796
805
|
type: "string",
|
|
797
806
|
version: "stringNumber",
|
|
798
|
-
hashedBytes: "number"
|
|
807
|
+
hashedBytes: "number",
|
|
808
|
+
secret: "optionalString"
|
|
799
809
|
};
|
|
800
810
|
for (const [field, type] of Object.entries(typeChecks)) {
|
|
801
811
|
if (!isType(requiredKeyGenerated[field])[type])
|