vaultkeeper 0.3.0 → 1.0.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.js CHANGED
@@ -3,6 +3,7 @@ import * as fs5 from 'fs/promises';
3
3
  import * as path5 from 'path';
4
4
  import * as os4 from 'os';
5
5
  import * as crypto4 from 'crypto';
6
+ import 'url';
6
7
  import * as fs4 from 'fs';
7
8
  import { CompactEncrypt, compactDecrypt } from 'jose';
8
9
 
@@ -136,6 +137,22 @@ var IdentityMismatchError = class extends VaultError {
136
137
  this.currentHash = currentHash;
137
138
  }
138
139
  };
140
+ var InvalidAlgorithmError = class extends VaultError {
141
+ /**
142
+ * The algorithm that was requested.
143
+ */
144
+ algorithm;
145
+ /**
146
+ * The set of algorithms that are allowed.
147
+ */
148
+ allowed;
149
+ constructor(message, algorithm, allowed) {
150
+ super(message);
151
+ this.name = "InvalidAlgorithmError";
152
+ this.algorithm = algorithm;
153
+ this.allowed = allowed;
154
+ }
155
+ };
139
156
  var SetupError = class extends VaultError {
140
157
  /**
141
158
  * The name of the dependency that caused the setup failure.
@@ -179,6 +196,7 @@ function isListableBackend(backend) {
179
196
  // src/backend/registry.ts
180
197
  var BackendRegistry = class {
181
198
  static backends = /* @__PURE__ */ new Map();
199
+ static setups = /* @__PURE__ */ new Map();
182
200
  /**
183
201
  * Register a backend factory.
184
202
  * @param type - Backend type identifier
@@ -191,7 +209,7 @@ var BackendRegistry = class {
191
209
  * Create a backend instance by type.
192
210
  * @param type - Backend type identifier
193
211
  * @returns A SecretBackend instance
194
- * @throws Error if the backend type is not registered
212
+ * @throws {@link BackendUnavailableError} if the backend type is not registered
195
213
  */
196
214
  static create(type) {
197
215
  const factory = this.backends.get(type);
@@ -238,6 +256,46 @@ var BackendRegistry = class {
238
256
  );
239
257
  return results.filter((type) => type !== null);
240
258
  }
259
+ /**
260
+ * Register a setup factory for a backend type.
261
+ * @param type - Backend type identifier
262
+ * @param factory - Factory function that creates a setup generator
263
+ */
264
+ static registerSetup(type, factory) {
265
+ this.setups.set(type, factory);
266
+ }
267
+ /**
268
+ * Get the setup factory for a backend type, if one is registered.
269
+ * @param type - Backend type identifier
270
+ * @returns The setup factory, or `undefined` if none is registered
271
+ */
272
+ static getSetup(type) {
273
+ return this.setups.get(type);
274
+ }
275
+ /**
276
+ * Check whether a setup factory is registered for the given backend type.
277
+ * @param type - Backend type identifier
278
+ * @returns `true` if a setup factory is registered
279
+ */
280
+ static hasSetup(type) {
281
+ return this.setups.has(type);
282
+ }
283
+ /**
284
+ * Clear all registered backend factories.
285
+ * Intended for use in tests only.
286
+ * @internal
287
+ */
288
+ static clearBackends() {
289
+ this.backends.clear();
290
+ }
291
+ /**
292
+ * Clear all registered setup factories.
293
+ * Intended for use in tests only.
294
+ * @internal
295
+ */
296
+ static clearSetups() {
297
+ this.setups.clear();
298
+ }
241
299
  };
242
300
  async function execCommand(command, args, options) {
243
301
  const result = await execCommandFull(command, args);
@@ -487,6 +545,21 @@ function validateBackendEntry(entry, index) {
487
545
  }
488
546
  result.path = entry.path;
489
547
  }
548
+ if (entry.options !== void 0) {
549
+ if (!isObject(entry.options)) {
550
+ throw new Error(`backends[${String(index)}].options must be an object`);
551
+ }
552
+ const opts = {};
553
+ for (const [k, v] of Object.entries(entry.options)) {
554
+ if (typeof v !== "string") {
555
+ throw new Error(
556
+ `backends[${String(index)}].options["${k}"] must be a string`
557
+ );
558
+ }
559
+ opts[k] = v;
560
+ }
561
+ result.options = opts;
562
+ }
490
563
  return result;
491
564
  }
492
565
  function validateConfig(config) {
@@ -990,6 +1063,55 @@ function createSecretAccessor(secretValue) {
990
1063
  return proxy;
991
1064
  }
992
1065
 
1066
+ // src/access/sign-util.ts
1067
+ var ALLOWED_ALGORITHMS = /* @__PURE__ */ new Set(["sha256", "sha384", "sha512"]);
1068
+ function resolveAlgorithmForKey(key, override) {
1069
+ const keyType = key.asymmetricKeyType;
1070
+ if (keyType === "ed25519" || keyType === "ed448") {
1071
+ return { signAlg: null, label: keyType };
1072
+ }
1073
+ const alg = (override ?? "sha256").toLowerCase();
1074
+ if (!ALLOWED_ALGORITHMS.has(alg)) {
1075
+ throw new InvalidAlgorithmError(
1076
+ `Unsupported algorithm '${alg}'. Allowed: ${[...ALLOWED_ALGORITHMS].join(", ")}`,
1077
+ alg,
1078
+ [...ALLOWED_ALGORITHMS]
1079
+ );
1080
+ }
1081
+ return { signAlg: alg, label: alg };
1082
+ }
1083
+
1084
+ // src/access/delegated-sign.ts
1085
+ function delegatedSign(secretPem, request) {
1086
+ const key = crypto4.createPrivateKey(secretPem);
1087
+ const { signAlg, label } = resolveAlgorithmForKey(key, request.algorithm);
1088
+ const data = Buffer.isBuffer(request.data) ? request.data : Buffer.from(request.data);
1089
+ const signature = crypto4.sign(signAlg, data, key);
1090
+ return {
1091
+ signature: signature.toString("base64"),
1092
+ algorithm: label
1093
+ };
1094
+ }
1095
+ function delegatedVerify(request) {
1096
+ let key;
1097
+ try {
1098
+ key = crypto4.createPublicKey(request.publicKey);
1099
+ } catch {
1100
+ return false;
1101
+ }
1102
+ if (typeof request.publicKey === "string" && request.publicKey.includes("PRIVATE KEY")) {
1103
+ return false;
1104
+ }
1105
+ const { signAlg } = resolveAlgorithmForKey(key, request.algorithm);
1106
+ const sig = Buffer.from(request.signature, "base64");
1107
+ try {
1108
+ const data = Buffer.isBuffer(request.data) ? request.data : Buffer.from(request.data);
1109
+ return crypto4.verify(signAlg, data, key, sig);
1110
+ } catch {
1111
+ return false;
1112
+ }
1113
+ }
1114
+
993
1115
  // src/doctor/checks.ts
994
1116
  function parseVersion(raw) {
995
1117
  const match = /(\d+)\.(\d+)\.(\d+)/.exec(raw);
@@ -1347,6 +1469,52 @@ var VaultKeeper = class _VaultKeeper {
1347
1469
  const claims = validateCapabilityToken(token);
1348
1470
  return createSecretAccessor(claims.val);
1349
1471
  }
1472
+ /**
1473
+ * Sign data using the private key embedded in a capability token.
1474
+ *
1475
+ * The signing key is extracted from the token's encrypted claims, used
1476
+ * for a single `crypto.sign()` call, and never exposed to the caller.
1477
+ * The algorithm is auto-detected from the key type unless overridden
1478
+ * in the request.
1479
+ *
1480
+ * @param token - A `CapabilityToken` obtained from `authorize()`.
1481
+ * @param request - The data to sign and optional algorithm override.
1482
+ * @returns The base64-encoded signature and algorithm label, together
1483
+ * with the vault metadata (`vaultResponse`).
1484
+ * @throws {VaultError} If `token` is invalid or was not created by this
1485
+ * vault instance.
1486
+ * @throws {InvalidAlgorithmError} If `request.algorithm` is not in the
1487
+ * allowed set (e.g. `'md5'`).
1488
+ */
1489
+ async sign(token, request) {
1490
+ const claims = validateCapabilityToken(token);
1491
+ const result = delegatedSign(claims.val, request);
1492
+ await Promise.resolve();
1493
+ return {
1494
+ result,
1495
+ vaultResponse: { keyStatus: "current" }
1496
+ };
1497
+ }
1498
+ /**
1499
+ * Verify a signature using a public key.
1500
+ *
1501
+ * This is a static method — no VaultKeeper instance, secrets, or
1502
+ * capability tokens are required. It is safe to call from CI or any
1503
+ * context that has access to public key material.
1504
+ *
1505
+ * Returns `false` for invalid key material, malformed signatures, or
1506
+ * any verification failure (except disallowed algorithms, which throw).
1507
+ *
1508
+ * @throws {InvalidAlgorithmError} If `request.algorithm` is not in the
1509
+ * allowed set (e.g. `'md5'`).
1510
+ *
1511
+ * @param request - The data, signature, public key, and optional
1512
+ * algorithm override.
1513
+ * @returns `true` if the signature is valid, `false` otherwise.
1514
+ */
1515
+ static verify(request) {
1516
+ return delegatedVerify(request);
1517
+ }
1350
1518
  /**
1351
1519
  * Rotate the current encryption key.
1352
1520
  *
@@ -1463,6 +1631,6 @@ var VaultKeeper = class _VaultKeeper {
1463
1631
  }
1464
1632
  };
1465
1633
 
1466
- export { AuthorizationDeniedError, BackendLockedError, BackendRegistry, BackendUnavailableError, CapabilityToken, DeviceNotPresentError, FilesystemError, IdentityMismatchError, KeyRevokedError, KeyRotatedError, PluginNotFoundError, RotationInProgressError, SecretNotFoundError, SetupError, TokenExpiredError, TokenRevokedError, UsageLimitExceededError, VaultError, VaultKeeper, isListableBackend };
1634
+ export { AuthorizationDeniedError, BackendLockedError, BackendRegistry, BackendUnavailableError, CapabilityToken, DeviceNotPresentError, FilesystemError, IdentityMismatchError, InvalidAlgorithmError, KeyRevokedError, KeyRotatedError, PluginNotFoundError, RotationInProgressError, SecretNotFoundError, SetupError, TokenExpiredError, TokenRevokedError, UsageLimitExceededError, VaultError, VaultKeeper, isListableBackend };
1467
1635
  //# sourceMappingURL=index.js.map
1468
1636
  //# sourceMappingURL=index.js.map