vaultkeeper 0.2.0 → 0.4.0

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/dist/index.d.cts CHANGED
@@ -287,6 +287,59 @@ interface SecretAccessor {
287
287
  */
288
288
  read(callback: (buf: Buffer) => void): void;
289
289
  }
290
+ /**
291
+ * Request for delegated signing.
292
+ *
293
+ * The `data` field is the payload to sign. Strings are UTF-8-encoded
294
+ * before signing.
295
+ */
296
+ interface SignRequest {
297
+ /** The data to sign. Strings are treated as UTF-8. */
298
+ data: string | Buffer;
299
+ /**
300
+ * Override the hash algorithm (`'sha256'`, `'sha384'`, or `'sha512'`).
301
+ * Ignored for Ed25519/Ed448 keys where the algorithm is implicit.
302
+ * Non-Edwards keys (RSA, EC) default to `'sha256'` when omitted.
303
+ * Weak algorithms (e.g. `'md5'`, `'sha1'`) are rejected.
304
+ */
305
+ algorithm?: string | undefined;
306
+ }
307
+ /** Result from a delegated signing operation. */
308
+ interface SignResult {
309
+ /** Base64-encoded signature. */
310
+ signature: string;
311
+ /**
312
+ * Algorithm label describing how the signature was produced.
313
+ * For Edwards keys this is the key type (e.g. `'ed25519'`).
314
+ * For other keys this matches the `algorithm` field from the request
315
+ * (or the default `'sha256'`).
316
+ */
317
+ algorithm: string;
318
+ }
319
+ /**
320
+ * Request for signature verification.
321
+ *
322
+ * This is a static operation that only requires public key material —
323
+ * no VaultKeeper instance or capability token is needed.
324
+ */
325
+ interface VerifyRequest {
326
+ /** The original data that was signed. Strings are treated as UTF-8. */
327
+ data: string | Buffer;
328
+ /** Base64-encoded signature to verify. */
329
+ signature: string;
330
+ /**
331
+ * PEM-encoded public key (SPKI format) as a string.
332
+ *
333
+ * Other `KeyLike` formats supported by `crypto.createPublicKey()` are not
334
+ * accepted by this interface.
335
+ */
336
+ publicKey: string;
337
+ /**
338
+ * Override the hash algorithm. Ignored for Ed25519/Ed448 keys.
339
+ * Non-Edwards keys default to `'sha256'` when omitted.
340
+ */
341
+ algorithm?: string | undefined;
342
+ }
290
343
  /** Vaultkeeper configuration file structure. */
291
344
  interface VaultConfig {
292
345
  /** Config schema version. Currently must be `1`. */
@@ -379,6 +432,22 @@ interface SecretBackend {
379
432
  */
380
433
  exists(id: string): Promise<boolean>;
381
434
  }
435
+ /**
436
+ * Backend that can enumerate stored secret IDs.
437
+ * @public
438
+ */
439
+ interface ListableBackend extends SecretBackend {
440
+ /**
441
+ * List IDs of all secrets managed by this backend.
442
+ * @returns Array of secret identifiers
443
+ */
444
+ list(): Promise<string[]>;
445
+ }
446
+ /**
447
+ * Type guard for backends that support listing.
448
+ * @public
449
+ */
450
+ declare function isListableBackend(backend: SecretBackend): backend is ListableBackend;
382
451
 
383
452
  /**
384
453
  * Registry for secret backend implementations.
@@ -418,6 +487,19 @@ declare class BackendRegistry {
418
487
  * @returns Array of backend type identifiers
419
488
  */
420
489
  static getTypes(): string[];
490
+ /**
491
+ * Returns backend types that are available on the current system.
492
+ *
493
+ * @remarks
494
+ * Creates each registered backend via its factory, calls `isAvailable()`,
495
+ * and returns only the type identifiers whose backend reports availability.
496
+ * If a backend's `isAvailable()` call throws, that backend is excluded from
497
+ * the result rather than propagating the error.
498
+ *
499
+ * @returns Promise resolving to an array of available backend type identifiers
500
+ * @public
501
+ */
502
+ static getAvailableTypes(): Promise<string[]>;
421
503
  }
422
504
 
423
505
  /**
@@ -549,6 +631,40 @@ declare class VaultKeeper {
549
631
  * instance.
550
632
  */
551
633
  getSecret(token: CapabilityToken): SecretAccessor;
634
+ /**
635
+ * Sign data using the private key embedded in a capability token.
636
+ *
637
+ * The signing key is extracted from the token's encrypted claims, used
638
+ * for a single `crypto.sign()` call, and never exposed to the caller.
639
+ * The algorithm is auto-detected from the key type unless overridden
640
+ * in the request.
641
+ *
642
+ * @param token - A `CapabilityToken` obtained from `authorize()`.
643
+ * @param request - The data to sign and optional algorithm override.
644
+ * @returns The base64-encoded signature and algorithm label, together
645
+ * with the vault metadata (`vaultResponse`).
646
+ * @throws {VaultError} If `token` is invalid or was not created by this
647
+ * vault instance.
648
+ */
649
+ sign(token: CapabilityToken, request: SignRequest): Promise<{
650
+ result: SignResult;
651
+ vaultResponse: VaultResponse;
652
+ }>;
653
+ /**
654
+ * Verify a signature using a public key.
655
+ *
656
+ * This is a static method — no VaultKeeper instance, secrets, or
657
+ * capability tokens are required. It is safe to call from CI or any
658
+ * context that has access to public key material.
659
+ *
660
+ * Never throws. Returns `false` for invalid key material, malformed
661
+ * signatures, or any verification failure.
662
+ *
663
+ * @param request - The data, signature, public key, and optional
664
+ * algorithm override.
665
+ * @returns `true` if the signature is valid, `false` otherwise.
666
+ */
667
+ static verify(request: VerifyRequest): boolean;
552
668
  /**
553
669
  * Rotate the current encryption key.
554
670
  *
@@ -584,4 +700,4 @@ declare class VaultKeeper {
584
700
  setDevelopmentMode(executablePath: string, enabled: boolean): Promise<void>;
585
701
  }
586
702
 
587
- export { AuthorizationDeniedError, type BackendConfig, type BackendFactory, BackendLockedError, BackendRegistry, BackendUnavailableError, CapabilityToken, DeviceNotPresentError, type ExecRequest, type ExecResult, type FetchRequest, FilesystemError, IdentityMismatchError, KeyRevokedError, KeyRotatedError, type KeyStatus, PluginNotFoundError, type PreflightCheck, type PreflightCheckStatus, type PreflightResult, RotationInProgressError, type SecretAccessor, type SecretBackend, SecretNotFoundError, SetupError, type SetupOptions, TokenExpiredError, TokenRevokedError, type TrustTier, UsageLimitExceededError, type VaultConfig, VaultError, VaultKeeper, type VaultKeeperOptions, type VaultResponse };
703
+ export { AuthorizationDeniedError, type BackendConfig, type BackendFactory, BackendLockedError, BackendRegistry, BackendUnavailableError, CapabilityToken, DeviceNotPresentError, type ExecRequest, type ExecResult, type FetchRequest, FilesystemError, IdentityMismatchError, KeyRevokedError, KeyRotatedError, type KeyStatus, type ListableBackend, PluginNotFoundError, type PreflightCheck, type PreflightCheckStatus, type PreflightResult, RotationInProgressError, type SecretAccessor, type SecretBackend, SecretNotFoundError, SetupError, type SetupOptions, type SignRequest, type SignResult, TokenExpiredError, TokenRevokedError, type TrustTier, UsageLimitExceededError, type VaultConfig, VaultError, VaultKeeper, type VaultKeeperOptions, type VaultResponse, type VerifyRequest, isListableBackend };
package/dist/index.d.ts CHANGED
@@ -287,6 +287,59 @@ interface SecretAccessor {
287
287
  */
288
288
  read(callback: (buf: Buffer) => void): void;
289
289
  }
290
+ /**
291
+ * Request for delegated signing.
292
+ *
293
+ * The `data` field is the payload to sign. Strings are UTF-8-encoded
294
+ * before signing.
295
+ */
296
+ interface SignRequest {
297
+ /** The data to sign. Strings are treated as UTF-8. */
298
+ data: string | Buffer;
299
+ /**
300
+ * Override the hash algorithm (`'sha256'`, `'sha384'`, or `'sha512'`).
301
+ * Ignored for Ed25519/Ed448 keys where the algorithm is implicit.
302
+ * Non-Edwards keys (RSA, EC) default to `'sha256'` when omitted.
303
+ * Weak algorithms (e.g. `'md5'`, `'sha1'`) are rejected.
304
+ */
305
+ algorithm?: string | undefined;
306
+ }
307
+ /** Result from a delegated signing operation. */
308
+ interface SignResult {
309
+ /** Base64-encoded signature. */
310
+ signature: string;
311
+ /**
312
+ * Algorithm label describing how the signature was produced.
313
+ * For Edwards keys this is the key type (e.g. `'ed25519'`).
314
+ * For other keys this matches the `algorithm` field from the request
315
+ * (or the default `'sha256'`).
316
+ */
317
+ algorithm: string;
318
+ }
319
+ /**
320
+ * Request for signature verification.
321
+ *
322
+ * This is a static operation that only requires public key material —
323
+ * no VaultKeeper instance or capability token is needed.
324
+ */
325
+ interface VerifyRequest {
326
+ /** The original data that was signed. Strings are treated as UTF-8. */
327
+ data: string | Buffer;
328
+ /** Base64-encoded signature to verify. */
329
+ signature: string;
330
+ /**
331
+ * PEM-encoded public key (SPKI format) as a string.
332
+ *
333
+ * Other `KeyLike` formats supported by `crypto.createPublicKey()` are not
334
+ * accepted by this interface.
335
+ */
336
+ publicKey: string;
337
+ /**
338
+ * Override the hash algorithm. Ignored for Ed25519/Ed448 keys.
339
+ * Non-Edwards keys default to `'sha256'` when omitted.
340
+ */
341
+ algorithm?: string | undefined;
342
+ }
290
343
  /** Vaultkeeper configuration file structure. */
291
344
  interface VaultConfig {
292
345
  /** Config schema version. Currently must be `1`. */
@@ -379,6 +432,22 @@ interface SecretBackend {
379
432
  */
380
433
  exists(id: string): Promise<boolean>;
381
434
  }
435
+ /**
436
+ * Backend that can enumerate stored secret IDs.
437
+ * @public
438
+ */
439
+ interface ListableBackend extends SecretBackend {
440
+ /**
441
+ * List IDs of all secrets managed by this backend.
442
+ * @returns Array of secret identifiers
443
+ */
444
+ list(): Promise<string[]>;
445
+ }
446
+ /**
447
+ * Type guard for backends that support listing.
448
+ * @public
449
+ */
450
+ declare function isListableBackend(backend: SecretBackend): backend is ListableBackend;
382
451
 
383
452
  /**
384
453
  * Registry for secret backend implementations.
@@ -418,6 +487,19 @@ declare class BackendRegistry {
418
487
  * @returns Array of backend type identifiers
419
488
  */
420
489
  static getTypes(): string[];
490
+ /**
491
+ * Returns backend types that are available on the current system.
492
+ *
493
+ * @remarks
494
+ * Creates each registered backend via its factory, calls `isAvailable()`,
495
+ * and returns only the type identifiers whose backend reports availability.
496
+ * If a backend's `isAvailable()` call throws, that backend is excluded from
497
+ * the result rather than propagating the error.
498
+ *
499
+ * @returns Promise resolving to an array of available backend type identifiers
500
+ * @public
501
+ */
502
+ static getAvailableTypes(): Promise<string[]>;
421
503
  }
422
504
 
423
505
  /**
@@ -549,6 +631,40 @@ declare class VaultKeeper {
549
631
  * instance.
550
632
  */
551
633
  getSecret(token: CapabilityToken): SecretAccessor;
634
+ /**
635
+ * Sign data using the private key embedded in a capability token.
636
+ *
637
+ * The signing key is extracted from the token's encrypted claims, used
638
+ * for a single `crypto.sign()` call, and never exposed to the caller.
639
+ * The algorithm is auto-detected from the key type unless overridden
640
+ * in the request.
641
+ *
642
+ * @param token - A `CapabilityToken` obtained from `authorize()`.
643
+ * @param request - The data to sign and optional algorithm override.
644
+ * @returns The base64-encoded signature and algorithm label, together
645
+ * with the vault metadata (`vaultResponse`).
646
+ * @throws {VaultError} If `token` is invalid or was not created by this
647
+ * vault instance.
648
+ */
649
+ sign(token: CapabilityToken, request: SignRequest): Promise<{
650
+ result: SignResult;
651
+ vaultResponse: VaultResponse;
652
+ }>;
653
+ /**
654
+ * Verify a signature using a public key.
655
+ *
656
+ * This is a static method — no VaultKeeper instance, secrets, or
657
+ * capability tokens are required. It is safe to call from CI or any
658
+ * context that has access to public key material.
659
+ *
660
+ * Never throws. Returns `false` for invalid key material, malformed
661
+ * signatures, or any verification failure.
662
+ *
663
+ * @param request - The data, signature, public key, and optional
664
+ * algorithm override.
665
+ * @returns `true` if the signature is valid, `false` otherwise.
666
+ */
667
+ static verify(request: VerifyRequest): boolean;
552
668
  /**
553
669
  * Rotate the current encryption key.
554
670
  *
@@ -584,4 +700,4 @@ declare class VaultKeeper {
584
700
  setDevelopmentMode(executablePath: string, enabled: boolean): Promise<void>;
585
701
  }
586
702
 
587
- export { AuthorizationDeniedError, type BackendConfig, type BackendFactory, BackendLockedError, BackendRegistry, BackendUnavailableError, CapabilityToken, DeviceNotPresentError, type ExecRequest, type ExecResult, type FetchRequest, FilesystemError, IdentityMismatchError, KeyRevokedError, KeyRotatedError, type KeyStatus, PluginNotFoundError, type PreflightCheck, type PreflightCheckStatus, type PreflightResult, RotationInProgressError, type SecretAccessor, type SecretBackend, SecretNotFoundError, SetupError, type SetupOptions, TokenExpiredError, TokenRevokedError, type TrustTier, UsageLimitExceededError, type VaultConfig, VaultError, VaultKeeper, type VaultKeeperOptions, type VaultResponse };
703
+ export { AuthorizationDeniedError, type BackendConfig, type BackendFactory, BackendLockedError, BackendRegistry, BackendUnavailableError, CapabilityToken, DeviceNotPresentError, type ExecRequest, type ExecResult, type FetchRequest, FilesystemError, IdentityMismatchError, KeyRevokedError, KeyRotatedError, type KeyStatus, type ListableBackend, PluginNotFoundError, type PreflightCheck, type PreflightCheckStatus, type PreflightResult, RotationInProgressError, type SecretAccessor, type SecretBackend, SecretNotFoundError, SetupError, type SetupOptions, type SignRequest, type SignResult, TokenExpiredError, TokenRevokedError, type TrustTier, UsageLimitExceededError, type VaultConfig, VaultError, VaultKeeper, type VaultKeeperOptions, type VaultResponse, type VerifyRequest, isListableBackend };
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import { spawn } from 'child_process';
2
2
  import * as fs5 from 'fs/promises';
3
3
  import * as path5 from 'path';
4
4
  import * as os4 from 'os';
5
- import * as crypto3 from 'crypto';
5
+ import * as crypto4 from 'crypto';
6
6
  import * as fs4 from 'fs';
7
7
  import { CompactEncrypt, compactDecrypt } from 'jose';
8
8
 
@@ -171,6 +171,11 @@ var RotationInProgressError = class extends VaultError {
171
171
  }
172
172
  };
173
173
 
174
+ // src/backend/types.ts
175
+ function isListableBackend(backend) {
176
+ return "list" in backend && typeof backend.list === "function";
177
+ }
178
+
174
179
  // src/backend/registry.ts
175
180
  var BackendRegistry = class {
176
181
  static backends = /* @__PURE__ */ new Map();
@@ -206,6 +211,33 @@ var BackendRegistry = class {
206
211
  static getTypes() {
207
212
  return Array.from(this.backends.keys());
208
213
  }
214
+ /**
215
+ * Returns backend types that are available on the current system.
216
+ *
217
+ * @remarks
218
+ * Creates each registered backend via its factory, calls `isAvailable()`,
219
+ * and returns only the type identifiers whose backend reports availability.
220
+ * If a backend's `isAvailable()` call throws, that backend is excluded from
221
+ * the result rather than propagating the error.
222
+ *
223
+ * @returns Promise resolving to an array of available backend type identifiers
224
+ * @public
225
+ */
226
+ static async getAvailableTypes() {
227
+ const entries = Array.from(this.backends.entries());
228
+ const results = await Promise.all(
229
+ entries.map(async ([type, factory]) => {
230
+ try {
231
+ const backend = factory();
232
+ const available = await backend.isAvailable();
233
+ return available ? type : null;
234
+ } catch {
235
+ return null;
236
+ }
237
+ })
238
+ );
239
+ return results.filter((type) => type !== null);
240
+ }
209
241
  };
210
242
  async function execCommand(command, args, options) {
211
243
  const result = await execCommandFull(command, args);
@@ -239,7 +271,7 @@ path5.join(".vaultkeeper", "file");
239
271
  path5.join(".vaultkeeper", "yubikey");
240
272
  function hashExecutable(filePath) {
241
273
  return new Promise((resolve, reject) => {
242
- const hash = crypto3.createHash("sha256");
274
+ const hash = crypto4.createHash("sha256");
243
275
  const stream = fs4.createReadStream(filePath);
244
276
  stream.on("data", (chunk) => {
245
277
  hash.update(chunk);
@@ -539,10 +571,10 @@ var KeyManager = class {
539
571
  #rotating = false;
540
572
  /** Generate a new 32-byte key with a timestamp-based id. */
541
573
  generateKey() {
542
- const randomSuffix = crypto3.randomBytes(4).toString("hex");
574
+ const randomSuffix = crypto4.randomBytes(4).toString("hex");
543
575
  return {
544
576
  id: `k-${String(Date.now())}-${randomSuffix}`,
545
- key: new Uint8Array(crypto3.randomBytes(32)),
577
+ key: new Uint8Array(crypto4.randomBytes(32)),
546
578
  createdAt: /* @__PURE__ */ new Date()
547
579
  };
548
580
  }
@@ -958,6 +990,50 @@ function createSecretAccessor(secretValue) {
958
990
  return proxy;
959
991
  }
960
992
 
993
+ // src/access/sign-util.ts
994
+ var ALLOWED_ALGORITHMS = /* @__PURE__ */ new Set(["sha256", "sha384", "sha512"]);
995
+ function resolveAlgorithmForKey(key, override) {
996
+ const keyType = key.asymmetricKeyType;
997
+ if (keyType === "ed25519" || keyType === "ed448") {
998
+ return { signAlg: null, label: keyType };
999
+ }
1000
+ const alg = override ?? "sha256";
1001
+ if (!ALLOWED_ALGORITHMS.has(alg)) {
1002
+ throw new VaultError(
1003
+ `Unsupported signing algorithm '${alg}'. Allowed: ${[...ALLOWED_ALGORITHMS].join(", ")}`
1004
+ );
1005
+ }
1006
+ return { signAlg: alg, label: alg };
1007
+ }
1008
+
1009
+ // src/access/delegated-sign.ts
1010
+ function delegatedSign(secretPem, request) {
1011
+ const key = crypto4.createPrivateKey(secretPem);
1012
+ const { signAlg, label } = resolveAlgorithmForKey(key, request.algorithm);
1013
+ const data = Buffer.isBuffer(request.data) ? request.data : Buffer.from(request.data);
1014
+ const signature = crypto4.sign(signAlg, data, key);
1015
+ return {
1016
+ signature: signature.toString("base64"),
1017
+ algorithm: label
1018
+ };
1019
+ }
1020
+ function delegatedVerify(request) {
1021
+ let key;
1022
+ try {
1023
+ key = crypto4.createPublicKey(request.publicKey);
1024
+ } catch {
1025
+ return false;
1026
+ }
1027
+ const { signAlg } = resolveAlgorithmForKey(key, request.algorithm);
1028
+ const sig = Buffer.from(request.signature, "base64");
1029
+ try {
1030
+ const data = Buffer.isBuffer(request.data) ? request.data : Buffer.from(request.data);
1031
+ return crypto4.verify(signAlg, data, key, sig);
1032
+ } catch {
1033
+ return false;
1034
+ }
1035
+ }
1036
+
961
1037
  // src/doctor/checks.ts
962
1038
  function parseVersion(raw) {
963
1039
  const match = /(\d+)\.(\d+)\.(\d+)/.exec(raw);
@@ -1210,7 +1286,7 @@ var VaultKeeper = class _VaultKeeper {
1210
1286
  }
1211
1287
  const now = Math.floor(Date.now() / 1e3);
1212
1288
  const claims = {
1213
- jti: crypto3.randomUUID(),
1289
+ jti: crypto4.randomUUID(),
1214
1290
  exp: now + ttlMinutes * 60,
1215
1291
  iat: now,
1216
1292
  sub: secretName,
@@ -1315,6 +1391,47 @@ var VaultKeeper = class _VaultKeeper {
1315
1391
  const claims = validateCapabilityToken(token);
1316
1392
  return createSecretAccessor(claims.val);
1317
1393
  }
1394
+ /**
1395
+ * Sign data using the private key embedded in a capability token.
1396
+ *
1397
+ * The signing key is extracted from the token's encrypted claims, used
1398
+ * for a single `crypto.sign()` call, and never exposed to the caller.
1399
+ * The algorithm is auto-detected from the key type unless overridden
1400
+ * in the request.
1401
+ *
1402
+ * @param token - A `CapabilityToken` obtained from `authorize()`.
1403
+ * @param request - The data to sign and optional algorithm override.
1404
+ * @returns The base64-encoded signature and algorithm label, together
1405
+ * with the vault metadata (`vaultResponse`).
1406
+ * @throws {VaultError} If `token` is invalid or was not created by this
1407
+ * vault instance.
1408
+ */
1409
+ async sign(token, request) {
1410
+ const claims = validateCapabilityToken(token);
1411
+ const result = delegatedSign(claims.val, request);
1412
+ await Promise.resolve();
1413
+ return {
1414
+ result,
1415
+ vaultResponse: { keyStatus: "current" }
1416
+ };
1417
+ }
1418
+ /**
1419
+ * Verify a signature using a public key.
1420
+ *
1421
+ * This is a static method — no VaultKeeper instance, secrets, or
1422
+ * capability tokens are required. It is safe to call from CI or any
1423
+ * context that has access to public key material.
1424
+ *
1425
+ * Never throws. Returns `false` for invalid key material, malformed
1426
+ * signatures, or any verification failure.
1427
+ *
1428
+ * @param request - The data, signature, public key, and optional
1429
+ * algorithm override.
1430
+ * @returns `true` if the signature is valid, `false` otherwise.
1431
+ */
1432
+ static verify(request) {
1433
+ return delegatedVerify(request);
1434
+ }
1318
1435
  /**
1319
1436
  * Rotate the current encryption key.
1320
1437
  *
@@ -1431,6 +1548,6 @@ var VaultKeeper = class _VaultKeeper {
1431
1548
  }
1432
1549
  };
1433
1550
 
1434
- export { AuthorizationDeniedError, BackendLockedError, BackendRegistry, BackendUnavailableError, CapabilityToken, DeviceNotPresentError, FilesystemError, IdentityMismatchError, KeyRevokedError, KeyRotatedError, PluginNotFoundError, RotationInProgressError, SecretNotFoundError, SetupError, TokenExpiredError, TokenRevokedError, UsageLimitExceededError, VaultError, VaultKeeper };
1551
+ export { AuthorizationDeniedError, BackendLockedError, BackendRegistry, BackendUnavailableError, CapabilityToken, DeviceNotPresentError, FilesystemError, IdentityMismatchError, KeyRevokedError, KeyRotatedError, PluginNotFoundError, RotationInProgressError, SecretNotFoundError, SetupError, TokenExpiredError, TokenRevokedError, UsageLimitExceededError, VaultError, VaultKeeper, isListableBackend };
1435
1552
  //# sourceMappingURL=index.js.map
1436
1553
  //# sourceMappingURL=index.js.map