@umbra-privacy/qos-ceremony 0.4.0 → 0.6.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.
Binary file
package/dist-cli/cli.js CHANGED
@@ -6437,10 +6437,10 @@ var init_hex_encoding = __esm({
6437
6437
  });
6438
6438
 
6439
6439
  // ../../../node_modules/.pnpm/@smithy+core@3.29.6/node_modules/@smithy/core/dist-es/submodules/serde/util-body-length/calculateBodyLength.js
6440
- var import_node_fs10, calculateBodyLength;
6440
+ var import_node_fs11, calculateBodyLength;
6441
6441
  var init_calculateBodyLength = __esm({
6442
6442
  "../../../node_modules/.pnpm/@smithy+core@3.29.6/node_modules/@smithy/core/dist-es/submodules/serde/util-body-length/calculateBodyLength.js"() {
6443
- import_node_fs10 = require("node:fs");
6443
+ import_node_fs11 = require("node:fs");
6444
6444
  calculateBodyLength = (body) => {
6445
6445
  if (!body) {
6446
6446
  return 0;
@@ -6453,11 +6453,11 @@ var init_calculateBodyLength = __esm({
6453
6453
  return body.size;
6454
6454
  } else if (typeof body.start === "number" && typeof body.end === "number") {
6455
6455
  return body.end + 1 - body.start;
6456
- } else if (body instanceof import_node_fs10.ReadStream) {
6456
+ } else if (body instanceof import_node_fs11.ReadStream) {
6457
6457
  if (body.path != null) {
6458
- return (0, import_node_fs10.lstatSync)(body.path).size;
6458
+ return (0, import_node_fs11.lstatSync)(body.path).size;
6459
6459
  } else if (typeof body.fd === "number") {
6460
- return (0, import_node_fs10.fstatSync)(body.fd).size;
6460
+ return (0, import_node_fs11.fstatSync)(body.fd).size;
6461
6461
  }
6462
6462
  }
6463
6463
  throw new Error(`Body Length computation failed for ${body}`);
@@ -6756,11 +6756,11 @@ var init_types = __esm({
6756
6756
  });
6757
6757
 
6758
6758
  // ../../../node_modules/.pnpm/@smithy+core@3.29.6/node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/getHomeDir.js
6759
- var import_node_os4, import_node_path7, homeDirCache, getHomeDirCacheKey, getHomeDir;
6759
+ var import_node_os4, import_node_path8, homeDirCache, getHomeDirCacheKey, getHomeDir;
6760
6760
  var init_getHomeDir = __esm({
6761
6761
  "../../../node_modules/.pnpm/@smithy+core@3.29.6/node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/getHomeDir.js"() {
6762
6762
  import_node_os4 = require("node:os");
6763
- import_node_path7 = require("node:path");
6763
+ import_node_path8 = require("node:path");
6764
6764
  homeDirCache = {};
6765
6765
  getHomeDirCacheKey = () => {
6766
6766
  if (process && process.geteuid) {
@@ -6769,7 +6769,7 @@ var init_getHomeDir = __esm({
6769
6769
  return "DEFAULT";
6770
6770
  };
6771
6771
  getHomeDir = () => {
6772
- const { HOME, USERPROFILE, HOMEPATH, HOMEDRIVE = `C:${import_node_path7.sep}` } = process.env;
6772
+ const { HOME, USERPROFILE, HOMEPATH, HOMEDRIVE = `C:${import_node_path8.sep}` } = process.env;
6773
6773
  if (HOME)
6774
6774
  return HOME;
6775
6775
  if (USERPROFILE)
@@ -6795,16 +6795,16 @@ var init_getProfileName = __esm({
6795
6795
  });
6796
6796
 
6797
6797
  // ../../../node_modules/.pnpm/@smithy+core@3.29.6/node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/getSSOTokenFilepath.js
6798
- var import_node_crypto, import_node_path8, getSSOTokenFilepath;
6798
+ var import_node_crypto, import_node_path9, getSSOTokenFilepath;
6799
6799
  var init_getSSOTokenFilepath = __esm({
6800
6800
  "../../../node_modules/.pnpm/@smithy+core@3.29.6/node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/getSSOTokenFilepath.js"() {
6801
6801
  import_node_crypto = require("node:crypto");
6802
- import_node_path8 = require("node:path");
6802
+ import_node_path9 = require("node:path");
6803
6803
  init_getHomeDir();
6804
6804
  getSSOTokenFilepath = (id) => {
6805
6805
  const hasher = (0, import_node_crypto.createHash)("sha1");
6806
6806
  const cacheName = hasher.update(id).digest("hex");
6807
- return (0, import_node_path8.join)(getHomeDir(), ".aws", "sso", "cache", `${cacheName}.json`);
6807
+ return (0, import_node_path9.join)(getHomeDir(), ".aws", "sso", "cache", `${cacheName}.json`);
6808
6808
  };
6809
6809
  }
6810
6810
  });
@@ -6859,24 +6859,24 @@ var init_getConfigData = __esm({
6859
6859
  });
6860
6860
 
6861
6861
  // ../../../node_modules/.pnpm/@smithy+core@3.29.6/node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/getConfigFilepath.js
6862
- var import_node_path9, ENV_CONFIG_PATH, getConfigFilepath;
6862
+ var import_node_path10, ENV_CONFIG_PATH, getConfigFilepath;
6863
6863
  var init_getConfigFilepath = __esm({
6864
6864
  "../../../node_modules/.pnpm/@smithy+core@3.29.6/node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/getConfigFilepath.js"() {
6865
- import_node_path9 = require("node:path");
6865
+ import_node_path10 = require("node:path");
6866
6866
  init_getHomeDir();
6867
6867
  ENV_CONFIG_PATH = "AWS_CONFIG_FILE";
6868
- getConfigFilepath = () => process.env[ENV_CONFIG_PATH] || (0, import_node_path9.join)(getHomeDir(), ".aws", "config");
6868
+ getConfigFilepath = () => process.env[ENV_CONFIG_PATH] || (0, import_node_path10.join)(getHomeDir(), ".aws", "config");
6869
6869
  }
6870
6870
  });
6871
6871
 
6872
6872
  // ../../../node_modules/.pnpm/@smithy+core@3.29.6/node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/getCredentialsFilepath.js
6873
- var import_node_path10, ENV_CREDENTIALS_PATH, getCredentialsFilepath;
6873
+ var import_node_path11, ENV_CREDENTIALS_PATH, getCredentialsFilepath;
6874
6874
  var init_getCredentialsFilepath = __esm({
6875
6875
  "../../../node_modules/.pnpm/@smithy+core@3.29.6/node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/getCredentialsFilepath.js"() {
6876
- import_node_path10 = require("node:path");
6876
+ import_node_path11 = require("node:path");
6877
6877
  init_getHomeDir();
6878
6878
  ENV_CREDENTIALS_PATH = "AWS_SHARED_CREDENTIALS_FILE";
6879
- getCredentialsFilepath = () => process.env[ENV_CREDENTIALS_PATH] || (0, import_node_path10.join)(getHomeDir(), ".aws", "credentials");
6879
+ getCredentialsFilepath = () => process.env[ENV_CREDENTIALS_PATH] || (0, import_node_path11.join)(getHomeDir(), ".aws", "credentials");
6880
6880
  }
6881
6881
  });
6882
6882
 
@@ -6956,10 +6956,10 @@ var init_readFile = __esm({
6956
6956
  });
6957
6957
 
6958
6958
  // ../../../node_modules/.pnpm/@smithy+core@3.29.6/node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/loadSharedConfigFiles.js
6959
- var import_node_path11, swallowError, loadSharedConfigFiles;
6959
+ var import_node_path12, swallowError, loadSharedConfigFiles;
6960
6960
  var init_loadSharedConfigFiles = __esm({
6961
6961
  "../../../node_modules/.pnpm/@smithy+core@3.29.6/node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/loadSharedConfigFiles.js"() {
6962
- import_node_path11 = require("node:path");
6962
+ import_node_path12 = require("node:path");
6963
6963
  init_getConfigData();
6964
6964
  init_getConfigFilepath();
6965
6965
  init_getCredentialsFilepath();
@@ -6974,11 +6974,11 @@ var init_loadSharedConfigFiles = __esm({
6974
6974
  const relativeHomeDirPrefix = "~/";
6975
6975
  let resolvedFilepath = filepath;
6976
6976
  if (filepath.startsWith(relativeHomeDirPrefix)) {
6977
- resolvedFilepath = (0, import_node_path11.join)(homeDir, filepath.slice(2));
6977
+ resolvedFilepath = (0, import_node_path12.join)(homeDir, filepath.slice(2));
6978
6978
  }
6979
6979
  let resolvedConfigFilepath = configFilepath;
6980
6980
  if (configFilepath.startsWith(relativeHomeDirPrefix)) {
6981
- resolvedConfigFilepath = (0, import_node_path11.join)(homeDir, configFilepath.slice(2));
6981
+ resolvedConfigFilepath = (0, import_node_path12.join)(homeDir, configFilepath.slice(2));
6982
6982
  }
6983
6983
  const parsedFiles = await Promise.all([
6984
6984
  readFile2(resolvedConfigFilepath, {
@@ -10082,17 +10082,17 @@ var init_HashCalculator = __esm({
10082
10082
  });
10083
10083
 
10084
10084
  // ../../../node_modules/.pnpm/@smithy+core@3.29.6/node_modules/@smithy/core/dist-es/submodules/checksum/hash-stream-node/fileStreamHasher.js
10085
- var import_node_fs11, fileStreamHasher, isReadStream;
10085
+ var import_node_fs12, fileStreamHasher, isReadStream;
10086
10086
  var init_fileStreamHasher = __esm({
10087
10087
  "../../../node_modules/.pnpm/@smithy+core@3.29.6/node_modules/@smithy/core/dist-es/submodules/checksum/hash-stream-node/fileStreamHasher.js"() {
10088
- import_node_fs11 = require("node:fs");
10088
+ import_node_fs12 = require("node:fs");
10089
10089
  init_HashCalculator();
10090
10090
  fileStreamHasher = (hashCtor, fileStream) => new Promise((resolve2, reject) => {
10091
10091
  if (!isReadStream(fileStream)) {
10092
10092
  reject(new Error("Unable to calculate hash for non-file streams."));
10093
10093
  return;
10094
10094
  }
10095
- const fileStreamTee = (0, import_node_fs11.createReadStream)(fileStream.path, {
10095
+ const fileStreamTee = (0, import_node_fs12.createReadStream)(fileStream.path, {
10096
10096
  start: fileStream.start,
10097
10097
  end: fileStream.end
10098
10098
  });
@@ -26697,7 +26697,7 @@ var require_dist_cjs11 = __commonJS({
26697
26697
  var { createHash: createHash5, createPrivateKey, createPublicKey, sign: sign2 } = require("node:crypto");
26698
26698
  var { promises } = require("node:fs");
26699
26699
  var { homedir: homedir3 } = require("node:os");
26700
- var { dirname: dirname5, join: join11 } = require("node:path");
26700
+ var { dirname: dirname6, join: join11 } = require("node:path");
26701
26701
  var LoginCredentialsFetcher = class _LoginCredentialsFetcher {
26702
26702
  profileData;
26703
26703
  init;
@@ -26845,7 +26845,7 @@ var require_dist_cjs11 = __commonJS({
26845
26845
  }
26846
26846
  async saveToken(token) {
26847
26847
  const tokenFilePath = this.getTokenFilePath();
26848
- const directory = dirname5(tokenFilePath);
26848
+ const directory = dirname6(tokenFilePath);
26849
26849
  try {
26850
26850
  await promises.mkdir(directory, { recursive: true });
26851
26851
  } catch (error2) {
@@ -32821,12 +32821,18 @@ var parseMembersDoc = (value) => {
32821
32821
  Object.entries(operators).map(([alias, entry]) => {
32822
32822
  if (!isRecord(entry))
32823
32823
  throw new Error(`members: '${alias}' is not an object`);
32824
- const { pubkeyHex, encryptionKey } = entry;
32824
+ const { pubkeyHex, encryptionKey, encryptionKeySalt } = entry;
32825
32825
  if (typeof pubkeyHex !== "string" || !HEX.test(pubkeyHex))
32826
32826
  throw new Error(`members: '${alias}' has no hex pubkeyHex`);
32827
32827
  if (encryptionKey !== void 0 && (typeof encryptionKey !== "string" || !HEX.test(encryptionKey)))
32828
32828
  throw new Error(`members: '${alias}' has a non-hex encryptionKey`);
32829
- const record = encryptionKey === void 0 ? { pubkeyHex } : { pubkeyHex, encryptionKey };
32829
+ if (encryptionKeySalt !== void 0 && (typeof encryptionKeySalt !== "string" || !HEX.test(encryptionKeySalt)))
32830
+ throw new Error(`members: '${alias}' has a non-hex encryptionKeySalt`);
32831
+ const record = {
32832
+ pubkeyHex,
32833
+ ...encryptionKey === void 0 ? {} : { encryptionKey },
32834
+ ...encryptionKeySalt === void 0 ? {} : { encryptionKeySalt }
32835
+ };
32830
32836
  return [alias, record];
32831
32837
  })
32832
32838
  );
@@ -33238,6 +33244,28 @@ var buildAtRestClient = (options) => {
33238
33244
  ],
33239
33245
  Math.max(timeout, MINT_AT_REST_TIMEOUT_MS)
33240
33246
  ).trim(),
33247
+ saltOf: (request) => run2(atRestBin, [
33248
+ "key",
33249
+ request.keyPath,
33250
+ "--print-salt",
33251
+ ...modeArgs(request.mode)
33252
+ ]).trim(),
33253
+ recoverKey: (request) => run2(
33254
+ atRestBin,
33255
+ [
33256
+ "key",
33257
+ request.outPath,
33258
+ "--mode",
33259
+ "hardware",
33260
+ "--salt",
33261
+ request.salt,
33262
+ "--expect-public",
33263
+ request.expectPublic,
33264
+ "--challenge-command",
33265
+ request.challengeCommand
33266
+ ],
33267
+ Math.max(timeout, MINT_AT_REST_TIMEOUT_MS)
33268
+ ).trim(),
33241
33269
  verifyKey: (request) => run2(atRestBin, [
33242
33270
  "key",
33243
33271
  request.keyPath,
@@ -33576,12 +33604,10 @@ var keygen = async (o3) => {
33576
33604
  createQosClient({ bin, timeoutMs: PROVISION_TIMEOUT_MS }),
33577
33605
  o3.provision === true
33578
33606
  );
33579
- const encryptionKey = ensureAtRestKey(
33580
- createAtRestFor(o3),
33581
- keys,
33582
- o3.challengeCommand
33583
- );
33584
- printRegistration(alias, pubHex, encryptionKey);
33607
+ const atRest = createAtRestFor(o3);
33608
+ const encryptionKey = ensureAtRestKey(atRest, keys, o3.challengeCommand);
33609
+ const salt = keys.mode === "hardware" ? atRest.saltOf({ keyPath: keys.encryptionKeyPath, mode: keys.mode }) : void 0;
33610
+ printRegistration(alias, pubHex, encryptionKey, salt);
33585
33611
  };
33586
33612
  var fileIdentity = (qos, secretPath) => {
33587
33613
  const pubPath = `${secretPath}.pub`;
@@ -33626,7 +33652,7 @@ var ensureAtRestKey = (atRest, keys, challengeCommand) => {
33626
33652
  );
33627
33653
  return minted;
33628
33654
  };
33629
- var printRegistration = (alias, pubHex, encryptionKey) => {
33655
+ var printRegistration = (alias, pubHex, encryptionKey, salt) => {
33630
33656
  console.log(
33631
33657
  "\nRegister this operator (admin, IAM-gated) so it can join ceremonies:"
33632
33658
  );
@@ -33634,7 +33660,7 @@ var printRegistration = (alias, pubHex, encryptionKey) => {
33634
33660
  " aws lambda invoke --function-name qos-coordinator-admin-<stack> --cli-binary-format raw-in-base64-out \\"
33635
33661
  );
33636
33662
  console.log(
33637
- ` --payload '{"action":"register-operator","service":"<namespace>","alias":"${alias}","pubkeyHex":"${pubHex}","encryptionKey":"${encryptionKey}","sets":["manifest","share","patch"]}' /dev/stdout`
33663
+ ` --payload '{"action":"register-operator","service":"<namespace>","alias":"${alias}","pubkeyHex":"${pubHex}","encryptionKey":"${encryptionKey}",` + (salt ? `"encryptionKeySalt":"${salt}",` : "") + `"sets":["manifest","share","patch"]}' /dev/stdout`
33638
33664
  );
33639
33665
  };
33640
33666
  var resolveYubikeyPublic = (qos, provisioner, mayProvision) => {
@@ -33893,15 +33919,82 @@ var provisionTvc = async (o3, deps = {
33893
33919
  }
33894
33920
  };
33895
33921
 
33896
- // src/commands/share.ts
33922
+ // src/commands/recover-key.ts
33897
33923
  var import_node_fs9 = require("node:fs");
33924
+ var import_node_path7 = require("node:path");
33925
+ var localFiles = {
33926
+ exists: import_node_fs9.existsSync,
33927
+ makeDirectory: (path) => {
33928
+ (0, import_node_fs9.mkdirSync)(path, { recursive: true, mode: 448 });
33929
+ }
33930
+ };
33931
+ var required2 = (value, name) => {
33932
+ if (!value) throw new Error(`recover-key requires ${name}`);
33933
+ return value;
33934
+ };
33935
+ var recoverKey = async (o3, deps) => {
33936
+ const alias = required2(o3.alias ?? process.env.CEREMONY_ALIAS, "--alias");
33937
+ const namespace = required2(
33938
+ o3.namespace ?? process.env.CEREMONY_NAMESPACE,
33939
+ "--namespace"
33940
+ );
33941
+ const keys = resolveOperatorKeys(o3, alias);
33942
+ if (keys.mode !== "hardware")
33943
+ throw new Error(
33944
+ "recover-key rebuilds a --yubikey key file from its registered salt; a --secret-path key file holds the seed itself, which no registry has, so it can only come from your backup"
33945
+ );
33946
+ const files = deps?.files ?? localFiles;
33947
+ if (files.exists(keys.encryptionKeyPath))
33948
+ throw new Error(
33949
+ `${keys.encryptionKeyPath} already exists \u2014 nothing to recover; \`keygen --yubikey\` re-derives it from the device to prove it still matches`
33950
+ );
33951
+ const { atRest, api } = deps ?? {
33952
+ files,
33953
+ atRest: createAtRestFor(o3),
33954
+ api: createCeremonyApi(
33955
+ required2(o3.api ?? process.env.CEREMONY_API, "--api")
33956
+ )
33957
+ };
33958
+ const record = (await api.getMemberRecords(namespace, 0))[alias];
33959
+ if (!record) throw new Error(`'${alias}' is not registered for ${namespace}`);
33960
+ if (!record.encryptionKey || !record.encryptionKeySalt)
33961
+ throw new Error(
33962
+ `the ${namespace} registry holds no encryptionKeySalt for '${alias}', so there is nothing to recover from \u2014 restore the key file from its backup, and re-register with the payload \`qos-ceremony keygen\` prints so the salt is on record next time`
33963
+ );
33964
+ files.makeDirectory((0, import_node_path7.dirname)(keys.encryptionKeyPath));
33965
+ atRest.recoverKey({
33966
+ outPath: keys.encryptionKeyPath,
33967
+ salt: record.encryptionKeySalt,
33968
+ expectPublic: record.encryptionKey,
33969
+ challengeCommand: o3.challengeCommand ?? DEFAULT_CHALLENGE_COMMAND
33970
+ });
33971
+ console.log(
33972
+ `recover-key: rebuilt ${keys.encryptionKeyPath} from the registered salt and this YubiKey; it derives the registered encryptionKey`
33973
+ );
33974
+ console.error(
33975
+ atRestCustodyNotice("recover-key", "hardware", keys.encryptionKeyPath)
33976
+ );
33977
+ };
33978
+
33979
+ // src/commands/share.ts
33980
+ var import_node_fs10 = require("node:fs");
33898
33981
  var requireEncryptionKeyPath = (ctx) => {
33899
- if (!(0, import_node_fs9.existsSync)(ctx.encryptionKeyPath))
33982
+ if (!(0, import_node_fs10.existsSync)(ctx.encryptionKeyPath))
33900
33983
  throw new Error(
33901
33984
  `no at-rest key at ${ctx.encryptionKeyPath} \u2014 the share is sealed under it before upload; run \`qos-ceremony keygen --alias ${ctx.alias} ${ctx.mode === "hardware" ? "--yubikey" : "--secret-path"}\` first`
33902
33985
  );
33903
33986
  return ctx.encryptionKeyPath;
33904
33987
  };
33988
+ var checkRegisteredSalt = (alias, localSalt, registered) => {
33989
+ if (!registered)
33990
+ throw new Error(
33991
+ `no encryptionKeySalt is registered for '${alias}', so nothing was uploaded \u2014 the registry must hold it before a share is frozen under your key, or a lost key file cannot be rebuilt; re-register with the payload \`qos-ceremony keygen\` prints`
33992
+ );
33993
+ if (registered.toLowerCase() !== localSalt.toLowerCase())
33994
+ throw new Error(
33995
+ `the encryptionKeySalt registered for '${alias}' is not your key file's \u2014 a mistyped salt would rebuild a key that opens nothing; re-register with replaceEncryptionKeySalt: true and the salt keygen prints`
33996
+ );
33997
+ };
33905
33998
  var checkRegisteredKey = (alias, localKey, registered) => {
33906
33999
  if (!registered)
33907
34000
  throw new Error(
@@ -34090,6 +34183,12 @@ var share = async (o3) => {
34090
34183
  ctx.atRest.publicKey({ keyPath: encryptionKeyPath, mode: ctx.mode }),
34091
34184
  record.encryptionKey
34092
34185
  );
34186
+ if (ctx.mode === "hardware")
34187
+ checkRegisteredSalt(
34188
+ ctx.alias,
34189
+ ctx.atRest.saltOf({ keyPath: encryptionKeyPath, mode: ctx.mode }),
34190
+ record.encryptionKeySalt
34191
+ );
34093
34192
  const wd = createWorkdir("ceremony-share-");
34094
34193
  try {
34095
34194
  if (o3.response)
@@ -34375,6 +34474,11 @@ withCommon(
34375
34474
  "with --yubikey, mint PIV 9c/9d only when qos_client reports them empty"
34376
34475
  )
34377
34476
  ).action(run(keygen));
34477
+ withCommon(
34478
+ program2.command("recover-key").description(
34479
+ "rebuild a lost --yubikey at-rest key file from the salt the registry holds and this YubiKey"
34480
+ )
34481
+ ).action(run(recoverKey));
34378
34482
  withCommon(
34379
34483
  program2.command("share").description(
34380
34484
  "upload your quorum-key share: lift it out of the quorum-gen response (--response), prove it opens under your at-rest key, and upload it double-sealed"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umbra-privacy/qos-ceremony",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "QOS TEE operator ceremony CLI — keygen/share/approve/provision + shift-traffic over the coordinator API, and the at-rest sealing tools. Shares are double-sealed everywhere they rest: ML-KEM-1024 over the QOS P-256 envelope. All crypto runs in embedded binaries (qos_client, umbra-at-rest); your private key never leaves the device. Ships NO coordinator code.",
5
5
  "keywords": [
6
6
  "qos",