@ibgib/core-gib 0.1.63 → 0.1.64

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.
Files changed (30) hide show
  1. package/dist/keystone/keystone-constants.d.mts +1 -0
  2. package/dist/keystone/keystone-constants.d.mts.map +1 -1
  3. package/dist/keystone/keystone-constants.mjs +1 -0
  4. package/dist/keystone/keystone-constants.mjs.map +1 -1
  5. package/dist/keystone/keystone-helpers.d.mts +19 -1
  6. package/dist/keystone/keystone-helpers.d.mts.map +1 -1
  7. package/dist/keystone/keystone-helpers.mjs +191 -16
  8. package/dist/keystone/keystone-helpers.mjs.map +1 -1
  9. package/dist/keystone/keystone-service-v1.d.mts +82 -1
  10. package/dist/keystone/keystone-service-v1.d.mts.map +1 -1
  11. package/dist/keystone/keystone-service-v1.mjs +321 -27
  12. package/dist/keystone/keystone-service-v1.mjs.map +1 -1
  13. package/dist/keystone/keystone-service-v1.respec.mjs +474 -47
  14. package/dist/keystone/keystone-service-v1.respec.mjs.map +1 -1
  15. package/dist/keystone/keystone-types.d.mts +26 -2
  16. package/dist/keystone/keystone-types.d.mts.map +1 -1
  17. package/dist/keystone/keystone-types.mjs +13 -0
  18. package/dist/keystone/keystone-types.mjs.map +1 -1
  19. package/dist/keystone/policy/profiles/profile-domain.json +0 -15
  20. package/dist/keystone/policy/schemas/keystone.high.schema.json +1 -1
  21. package/dist/keystone/policy/schemas/keystone.medium.schema.json +1 -1
  22. package/package.json +2 -2
  23. package/src/keystone/keystone-constants.mts +1 -0
  24. package/src/keystone/keystone-helpers.mts +223 -15
  25. package/src/keystone/keystone-service-v1.mts +387 -28
  26. package/src/keystone/keystone-service-v1.respec.mts +1990 -1500
  27. package/src/keystone/keystone-types.mts +43 -2
  28. package/src/keystone/policy/profiles/profile-domain.json +1 -16
  29. package/src/keystone/policy/schemas/keystone.high.schema.json +1 -1
  30. package/src/keystone/policy/schemas/keystone.medium.schema.json +1 -1
@@ -1,4 +1,4 @@
1
- import { extractErrorMsg, hash, HashAlgorithm, pretty } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
1
+ import { extractErrorMsg, hash, HashAlgorithm, pretty, getUUID } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
2
2
  import { GIB } from "@ibgib/ts-gib/dist/V1/constants.mjs";
3
3
  import { Ib, IbGibAddr, TransformResult } from "@ibgib/ts-gib/dist/types.mjs";
4
4
  import { getIbAndGib, getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs";
@@ -8,7 +8,7 @@ import { Factory_V1 } from "@ibgib/ts-gib/dist/V1/factory.mjs";
8
8
  import { getGib } from "@ibgib/ts-gib/dist/V1/transforms/transform-helper.mjs";
9
9
 
10
10
  import { GLOBAL_LOG_A_LOT } from "../core-constants.mjs";
11
- import { KEYSTONE_ATOM, KEYSTONE_HASH_MAX_ROUNDS, KEYSTONE_POOL_ID_REGEXP, KEYSTONE_SALT_REGEXP, KEYSTONE_USERNAME_REGEXP, KEYSTONE_DESCRIPTION_REGEXP, KEYSTONE_VERB_MANAGE } from "./keystone-constants.mjs";
11
+ import { KEYSTONE_ATOM, KEYSTONE_HASH_MAX_ROUNDS, KEYSTONE_POOL_ID_REGEXP, KEYSTONE_SALT_REGEXP, KEYSTONE_USERNAME_REGEXP, KEYSTONE_DESCRIPTION_REGEXP, KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE } from "./keystone-constants.mjs";
12
12
  import {
13
13
  KeystoneData_V1, KeystoneIbGib_V1, KeystoneIb_V1, KeystoneChallengePool,
14
14
  DeterministicResult, KeystoneProof, KeystonePoolConfig,
@@ -18,6 +18,10 @@ import {
18
18
  KEYSTONE_CHALLENGE_TYPE_VALID_VALUES,
19
19
  KeystonePoolConfig_HashV1,
20
20
  DelegateKeystoneInfo,
21
+ ClaimDetails,
22
+ ClaimDetails_AddPool,
23
+ ClaimDetails_ReplacePool,
24
+ ClaimDetails_ChangePassword
21
25
  } from "./keystone-types.mjs";
22
26
  import { MetaspaceService } from "../witness/space/metaspace/metaspace-types.mjs";
23
27
  import { IbGibSpaceAny } from "../witness/space/space-base-v1.mjs";
@@ -360,6 +364,7 @@ export async function applyReplenishmentStrategy({
360
364
  masterSecret,
361
365
  strategy,
362
366
  config,
367
+ verb,
363
368
  }: {
364
369
  prevPools: KeystoneChallengePool[],
365
370
  targetPoolId: string,
@@ -370,6 +375,7 @@ export async function applyReplenishmentStrategy({
370
375
  */
371
376
  strategy: any, // Typed as 'any' or 'KeystoneStrategyAny' to avoid circular dep if possible
372
377
  config: KeystonePoolConfig,
378
+ verb?: string,
373
379
  }): Promise<KeystoneChallengePool[]> {
374
380
  const lc = `[applyReplenishmentStrategy]`;
375
381
  try {
@@ -381,7 +387,10 @@ export async function applyReplenishmentStrategy({
381
387
  const poolSecret = await strategy.derivePoolSecret({ masterSecret });
382
388
  const timestamp = Date.now().toString();
383
389
 
384
- const strategyType = config.behavior.replenish;
390
+ let strategyType = config.behavior.replenish;
391
+ if (verb === KEYSTONE_VERB_REVOKE) {
392
+ strategyType = KeystoneReplenishStrategy.deleteAll;
393
+ }
385
394
 
386
395
  if (strategyType === KeystoneReplenishStrategy.topUp) {
387
396
  // Remove consumed
@@ -399,17 +408,10 @@ export async function applyReplenishmentStrategy({
399
408
  pool.challenges[newId] = await strategy.generateChallenge({ solution });
400
409
  }
401
410
  } else if (strategyType === KeystoneReplenishStrategy.replaceAll) {
402
- pool.challenges = {};
403
-
404
- for (let i = 0; i < config.behavior.size; i++) {
405
- const newId = await generateOpaqueChallengeId({
406
- salt: config.salt, timestamp, index: i
407
- });
408
- const solution = await strategy.generateSolution({
409
- poolSecret, poolId: pool.id, challengeId: newId
410
- });
411
- pool.challenges[newId] = await strategy.generateChallenge({ solution });
412
- }
411
+ pool.challenges = await generatePoolChallenges({
412
+ config,
413
+ masterSecret
414
+ });
413
415
  } else if (strategyType === KeystoneReplenishStrategy.consume) {
414
416
  consumedIds.forEach(id => delete pool.challenges[id]);
415
417
  } else if (strategyType === KeystoneReplenishStrategy.deleteAll) {
@@ -490,7 +492,8 @@ export async function solveAndReplenish({
490
492
  consumedIds: challengeIds,
491
493
  masterSecret,
492
494
  strategy,
493
- config: pool.config
495
+ config: pool.config,
496
+ verb: claim.verb
494
497
  });
495
498
 
496
499
  return { proof, nextPools };
@@ -873,6 +876,145 @@ export async function validateKeystoneTransition({
873
876
  }
874
877
  }
875
878
 
879
+ // Claim details verification (transition validation)
880
+ for (const proof of currData.proofs) {
881
+ const claim = proof.claim;
882
+ if (claim?.details) {
883
+ const details = claim.details;
884
+ const type = details.type;
885
+ const info = details.info;
886
+
887
+ if (type === 'add-pool') {
888
+ // Cast info to ClaimDetails_AddPool
889
+ const addInfo = info as ClaimDetails_AddPool;
890
+ if (!addInfo || !Array.isArray(addInfo.add) || addInfo.add.length === 0) {
891
+ errors.push(`details type is '${type}' but payload is invalid. (E: 1fa2a8cb28c9b60e68994511a5825)`);
892
+ continue;
893
+ }
894
+
895
+ // Verify that the set of pools in current equals previous plus the added pools
896
+ const prevIds = new Set(prevData.challengePools.map(p => p.id));
897
+ const currIds = new Set(currData.challengePools.map(p => p.id));
898
+
899
+ // All added pools must be in current
900
+ for (const id of addInfo.add) {
901
+ if (!currIds.has(id)) {
902
+ errors.push(`ClaimDetails_AddPool lists pool ${id} but it is missing in the evolved keystone. (E: 2fa2a8cb28c9b60e68994511a5825)`);
903
+ }
904
+ if (prevIds.has(id)) {
905
+ errors.push(`ClaimDetails_AddPool attempts to add existing pool ${id}. (E: 3fa2a8cb28c9b60e68994511a5825)`);
906
+ }
907
+ }
908
+
909
+ // Verify size
910
+ if (currData.challengePools.length !== prevData.challengePools.length + addInfo.add.length) {
911
+ errors.push(`Structural mismatch: expected ${prevData.challengePools.length + addInfo.add.length} pools, found ${currData.challengePools.length}. (E: 4fa2a8cb28c9b60e68994511a5825)`);
912
+ }
913
+
914
+ // Verify all previous pools are unmodified
915
+ for (const prevPool of prevData.challengePools) {
916
+ const currPool = currData.challengePools.find(p => p.id === prevPool.id);
917
+ if (!currPool) {
918
+ errors.push(`Existing pool ${prevPool.id} was removed during add-pool transition. (E: 5fa2a8cb28c9b60e68994511a5825)`);
919
+ continue;
920
+ }
921
+ // Verify configuration and challenges did not change
922
+ if (JSON.stringify(prevPool.config) !== JSON.stringify(currPool.config)) {
923
+ errors.push(`Existing pool config ${prevPool.id} was mutated during add-pool transition. (E: 6fa2a8cb28c9b60e68994511a5825)`);
924
+ }
925
+ if (JSON.stringify(prevPool.challenges) !== JSON.stringify(currPool.challenges)) {
926
+ errors.push(`Existing pool challenges ${prevPool.id} were mutated during add-pool transition. (E: 7fa2a8cb28c9b60e68994511a5825)`);
927
+ }
928
+ }
929
+
930
+ } else if (type === 'replace-pool') {
931
+ const replaceInfo = info as ClaimDetails_ReplacePool;
932
+ if (!replaceInfo || typeof replaceInfo.replace !== 'string' || !replaceInfo.replace) {
933
+ errors.push(`details type is '${type}' but payload is invalid. (E: 8fa2a8cb28c9b60e68994511a5825)`);
934
+ continue;
935
+ }
936
+
937
+ const targetId = replaceInfo.replace;
938
+
939
+ // Verify that the set of pool IDs in current equals previous
940
+ const prevIds = prevData.challengePools.map(p => p.id).sort();
941
+ const currIds = currData.challengePools.map(p => p.id).sort();
942
+ if (JSON.stringify(prevIds) !== JSON.stringify(currIds)) {
943
+ errors.push(`replace-pool transition mutated the pool IDs set. (E: 9fa2a8cb28c9b60e68994511a5825)`);
944
+ }
945
+
946
+ // Assert target pool was replaced (challenges modified)
947
+ const prevTarget = prevData.challengePools.find(p => p.id === targetId);
948
+ const currTarget = currData.challengePools.find(p => p.id === targetId);
949
+ if (!prevTarget || !currTarget) {
950
+ errors.push(`Replaced pool ${targetId} not found in keystone challenge pools. (E: afa2a8cb28c9b60e68994511a5825)`);
951
+ }
952
+
953
+ // Verify all other pools are completely unmodified
954
+ for (const prevPool of prevData.challengePools) {
955
+ if (prevPool.id === targetId) continue;
956
+ const currPool = currData.challengePools.find(p => p.id === prevPool.id);
957
+ if (!currPool) continue;
958
+
959
+ if (JSON.stringify(prevPool.config) !== JSON.stringify(currPool.config)) {
960
+ errors.push(`Unrelated pool config ${prevPool.id} was mutated during replace-pool. (E: bfa2a8cb28c9b60e68994511a5825)`);
961
+ }
962
+ if (JSON.stringify(prevPool.challenges) !== JSON.stringify(currPool.challenges)) {
963
+ errors.push(`Unrelated pool challenges ${prevPool.id} were mutated during replace-pool. (E: cfa2a8cb28c9b60e68994511a5825)`);
964
+ }
965
+ }
966
+
967
+ } else if (type === 'change-password') {
968
+ const changeInfo = info as ClaimDetails_ChangePassword;
969
+ if (!changeInfo || !Array.isArray(changeInfo.change) || changeInfo.change.length === 0) {
970
+ errors.push(`details type is '${type}' but payload is invalid. (E: dfa2a8cb28c9b60e68994511a5825)`);
971
+ continue;
972
+ }
973
+
974
+ // Verify that the set of pool IDs is unmodified
975
+ const prevIds = prevData.challengePools.map(p => p.id).sort();
976
+ const currIds = currData.challengePools.map(p => p.id).sort();
977
+ if (JSON.stringify(prevIds) !== JSON.stringify(currIds)) {
978
+ errors.push(`change-password transition mutated the pool IDs set. (E: efa2a8cb28c9b60e68994511a5825)`);
979
+ }
980
+
981
+ const rotatedIds = new Set(changeInfo.change);
982
+
983
+ for (const prevPool of prevData.challengePools) {
984
+ const currPool = currData.challengePools.find(p => p.id === prevPool.id);
985
+ if (!currPool) continue;
986
+
987
+ if (rotatedIds.has(prevPool.id)) {
988
+ // Check: rotated pool must not be foreign!
989
+ if (prevPool.isForeign) {
990
+ errors.push(`Security Violation: Password rotation attempted on foreign pool ${prevPool.id}. (E: ffa2a8cb28c9b60e68994511a5825)`);
991
+ }
992
+ // Config must be identical except for the salt
993
+ const prevConfCopy = { ...prevPool.config, salt: '' };
994
+ const currConfCopy = { ...currPool.config, salt: '' };
995
+ if (JSON.stringify(prevConfCopy) !== JSON.stringify(currConfCopy)) {
996
+ errors.push(`Pool config metadata for rotated pool ${prevPool.id} was mutated. (E: 00b2a8cb28c9b60e68994511a5825)`);
997
+ }
998
+ // Salt must be different
999
+ if (prevPool.config.salt === currPool.config.salt) {
1000
+ errors.push(`Pool salt for rotated pool ${prevPool.id} was not rotated. (E: 01b2a8cb28c9b60e68994511a5825)`);
1001
+ }
1002
+ } else {
1003
+ // Non-rotated pools must be completely unmodified
1004
+ if (JSON.stringify(prevPool.config) !== JSON.stringify(currPool.config)) {
1005
+ errors.push(`Unrelated pool config ${prevPool.id} was mutated during change-password. (E: 02b2a8cb28c9b60e68994511a5825)`);
1006
+ }
1007
+ if (JSON.stringify(prevPool.challenges) !== JSON.stringify(currPool.challenges)) {
1008
+ errors.push(`Unrelated pool challenges ${prevPool.id} were mutated during change-password. (E: 03b2a8cb28c9b60e68994511a5825)`);
1009
+ }
1010
+ }
1011
+ }
1012
+ } else {
1013
+ errors.push(`Unknown claim details type: ${type}`);
1014
+ }
1015
+ }
1016
+ }
1017
+
876
1018
  return errors;
877
1019
  } catch (error) {
878
1020
  console.error(`${lc} ${extractErrorMsg(error)}`);
@@ -1280,4 +1422,70 @@ export async function getIdentity_throwIfUndefined({
1280
1422
  const identityIbGib = await getIdentity({ metaspace, addr, space });
1281
1423
  if (!identityIbGib) { throw new Error(`addr (${addr}) not found in space: ${space.ib} (E: 7533682e805819cc78bdb0d8960be826)`); }
1282
1424
  return identityIbGib;
1425
+ }
1426
+
1427
+ /**
1428
+ * Pure helper to generate challenges map for a pool configuration using a masterSecret.
1429
+ */
1430
+ export async function generatePoolChallenges({
1431
+ config,
1432
+ masterSecret
1433
+ }: {
1434
+ config: KeystonePoolConfig;
1435
+ masterSecret: string;
1436
+ }): Promise<{ [challengeId: string]: any }> {
1437
+ const strategy = KeystoneStrategyFactory.create({ config });
1438
+ const poolSecret = await strategy.derivePoolSecret({ masterSecret });
1439
+ const challenges: { [challengeId: string]: any } = {};
1440
+ const targetSize = config.behavior.size;
1441
+ const timestamp = Date.now().toString();
1442
+
1443
+ for (let i = 0; i < targetSize; i++) {
1444
+ const challengeId = await generateOpaqueChallengeId({
1445
+ salt: config.salt,
1446
+ timestamp,
1447
+ index: i
1448
+ });
1449
+ const solution = await strategy.generateSolution({
1450
+ poolSecret,
1451
+ poolId: config.id,
1452
+ challengeId
1453
+ });
1454
+ challenges[challengeId] = await strategy.generateChallenge({ solution });
1455
+ }
1456
+ return challenges;
1457
+ }
1458
+
1459
+ /**
1460
+ * Pure helper to rotate a pool to a new masterSecret.
1461
+ * Takes the previous pool config, generates a new salt, and builds a new pool with refreshed challenges.
1462
+ */
1463
+ export async function rotatePoolChallenges({
1464
+ prevPool,
1465
+ newMasterSecret
1466
+ }: {
1467
+ prevPool: KeystoneChallengePool;
1468
+ newMasterSecret: string;
1469
+ }): Promise<KeystoneChallengePool> {
1470
+ const prevConfig = prevPool.config;
1471
+ const newSalt = (await getUUID()).substring(0, 16);
1472
+
1473
+ // Copy the config exactly, updating only the salt
1474
+ const newConfig: KeystonePoolConfig = {
1475
+ ...prevConfig,
1476
+ salt: newSalt
1477
+ };
1478
+
1479
+ const newChallenges = await generatePoolChallenges({
1480
+ config: newConfig,
1481
+ masterSecret: newMasterSecret
1482
+ });
1483
+
1484
+ return {
1485
+ id: prevPool.id,
1486
+ config: newConfig,
1487
+ challenges: newChallenges,
1488
+ ...(prevPool.isForeign !== undefined ? { isForeign: prevPool.isForeign } : {}),
1489
+ ...(prevPool.metadata ? { metadata: prevPool.metadata } : {})
1490
+ };
1283
1491
  }