@ibgib/core-gib 0.1.62 → 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 (102) 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 +84 -2
  10. package/dist/keystone/keystone-service-v1.d.mts.map +1 -1
  11. package/dist/keystone/keystone-service-v1.mjs +324 -28
  12. package/dist/keystone/keystone-service-v1.mjs.map +1 -1
  13. package/dist/keystone/keystone-service-v1.respec.mjs +447 -2
  14. package/dist/keystone/keystone-service-v1.respec.mjs.map +1 -1
  15. package/dist/keystone/keystone-types.d.mts +31 -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/keystone-profile-builder.d.mts +1 -1
  20. package/dist/keystone/policy/keystone-profile-builder.d.mts.map +1 -1
  21. package/dist/keystone/policy/keystone-profile-builder.mjs +8 -0
  22. package/dist/keystone/policy/keystone-profile-builder.mjs.map +1 -1
  23. package/dist/keystone/policy/profiles/profile-domain.json +69 -0
  24. package/dist/keystone/policy/profiles/profile-sync.json +84 -0
  25. package/dist/keystone/policy/schemas/keystone.high.schema.json +1 -1
  26. package/dist/keystone/policy/schemas/keystone.medium.schema.json +1 -1
  27. package/dist/keystone/strategy/hash-reveal-v1/hash-reveal-v1.d.mts +8 -0
  28. package/dist/keystone/strategy/hash-reveal-v1/hash-reveal-v1.d.mts.map +1 -1
  29. package/dist/keystone/strategy/hash-reveal-v1/hash-reveal-v1.mjs +15 -1
  30. package/dist/keystone/strategy/hash-reveal-v1/hash-reveal-v1.mjs.map +1 -1
  31. package/dist/sync/sync-conflict-adv-multitimelines.withid.respec.mjs +16 -4
  32. package/dist/sync/sync-conflict-adv-multitimelines.withid.respec.mjs.map +1 -1
  33. package/dist/sync/sync-conflict-basic-divergence.withid.respec.mjs +4 -1
  34. package/dist/sync/sync-conflict-basic-divergence.withid.respec.mjs.map +1 -1
  35. package/dist/sync/sync-conflict-basic-multitimelines.withid.respec.mjs +4 -1
  36. package/dist/sync/sync-conflict-basic-multitimelines.withid.respec.mjs.map +1 -1
  37. package/dist/sync/sync-conflict-text-merge.withid.respec.mjs +10 -4
  38. package/dist/sync/sync-conflict-text-merge.withid.respec.mjs.map +1 -1
  39. package/dist/sync/sync-innerspace-constants.withid.respec.mjs +12 -3
  40. package/dist/sync/sync-innerspace-constants.withid.respec.mjs.map +1 -1
  41. package/dist/sync/sync-innerspace-deep-updates.withid.respec.mjs +4 -1
  42. package/dist/sync/sync-innerspace-deep-updates.withid.respec.mjs.map +1 -1
  43. package/dist/sync/sync-innerspace-dest-ahead.withid.respec.mjs +4 -1
  44. package/dist/sync/sync-innerspace-dest-ahead.withid.respec.mjs.map +1 -1
  45. package/dist/sync/sync-innerspace-multiple-timelines.withid.respec.mjs +4 -1
  46. package/dist/sync/sync-innerspace-multiple-timelines.withid.respec.mjs.map +1 -1
  47. package/dist/sync/sync-innerspace-partial-update.withid.respec.mjs +4 -1
  48. package/dist/sync/sync-innerspace-partial-update.withid.respec.mjs.map +1 -1
  49. package/dist/sync/sync-innerspace.withid.respec.mjs +4 -1
  50. package/dist/sync/sync-innerspace.withid.respec.mjs.map +1 -1
  51. package/dist/sync/sync-peer/sync-peer-types.d.mts +4 -0
  52. package/dist/sync/sync-peer/sync-peer-types.d.mts.map +1 -1
  53. package/dist/sync/sync-peer/sync-peer-v1.d.mts.map +1 -1
  54. package/dist/sync/sync-peer/sync-peer-v1.mjs +4 -1
  55. package/dist/sync/sync-peer/sync-peer-v1.mjs.map +1 -1
  56. package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-receiver/sync-websocket-peer-helpers.d.mts.map +1 -1
  57. package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-receiver/sync-websocket-peer-helpers.mjs +90 -22
  58. package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-receiver/sync-websocket-peer-helpers.mjs.map +1 -1
  59. package/dist/sync/sync-saga-coordinator.d.mts +7 -10
  60. package/dist/sync/sync-saga-coordinator.d.mts.map +1 -1
  61. package/dist/sync/sync-saga-coordinator.mjs +14 -5
  62. package/dist/sync/sync-saga-coordinator.mjs.map +1 -1
  63. package/dist/sync/sync-types.d.mts +5 -0
  64. package/dist/sync/sync-types.d.mts.map +1 -1
  65. package/dist/sync/sync-types.mjs.map +1 -1
  66. package/dist/sync/sync-withid.connect.respec.mjs +4 -1
  67. package/dist/sync/sync-withid.connect.respec.mjs.map +1 -1
  68. package/dist/sync/sync-withid.establish.respec.mjs +4 -1
  69. package/dist/sync/sync-withid.establish.respec.mjs.map +1 -1
  70. package/dist/sync/sync-withid.pingpong.respec.mjs +4 -1
  71. package/dist/sync/sync-withid.pingpong.respec.mjs.map +1 -1
  72. package/package.json +2 -2
  73. package/src/keystone/keystone-constants.mts +1 -0
  74. package/src/keystone/keystone-helpers.mts +223 -15
  75. package/src/keystone/keystone-service-v1.mts +391 -28
  76. package/src/keystone/keystone-service-v1.respec.mts +1990 -1479
  77. package/src/keystone/keystone-types.mts +48 -2
  78. package/src/keystone/policy/keystone-profile-builder.mts +9 -1
  79. package/src/keystone/policy/profiles/profile-domain.json +69 -0
  80. package/src/keystone/policy/profiles/profile-sync.json +84 -0
  81. package/src/keystone/policy/schemas/keystone.high.schema.json +1 -1
  82. package/src/keystone/policy/schemas/keystone.medium.schema.json +1 -1
  83. package/src/keystone/strategy/hash-reveal-v1/hash-reveal-v1.mts +20 -1
  84. package/src/sync/sync-conflict-adv-multitimelines.withid.respec.mts +16 -4
  85. package/src/sync/sync-conflict-basic-divergence.withid.respec.mts +4 -1
  86. package/src/sync/sync-conflict-basic-multitimelines.withid.respec.mts +4 -1
  87. package/src/sync/sync-conflict-text-merge.withid.respec.mts +10 -4
  88. package/src/sync/sync-innerspace-constants.withid.respec.mts +12 -3
  89. package/src/sync/sync-innerspace-deep-updates.withid.respec.mts +4 -1
  90. package/src/sync/sync-innerspace-dest-ahead.withid.respec.mts +4 -1
  91. package/src/sync/sync-innerspace-multiple-timelines.withid.respec.mts +4 -1
  92. package/src/sync/sync-innerspace-partial-update.withid.respec.mts +4 -1
  93. package/src/sync/sync-innerspace.withid.respec.mts +4 -1
  94. package/src/sync/sync-peer/sync-peer-types.mts +4 -0
  95. package/src/sync/sync-peer/sync-peer-v1.mts +5 -1
  96. package/src/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-receiver/sync-websocket-peer-helpers.mts +96 -22
  97. package/src/sync/sync-saga-coordinator.mts +17 -12
  98. package/src/sync/sync-types.mts +6 -0
  99. package/src/sync/sync-withid.connect.respec.mts +5 -2
  100. package/src/sync/sync-withid.establish.respec.mts +4 -1
  101. package/src/sync/sync-withid.pingpong.respec.mts +4 -1
  102. package/src/sync/unused-identity-backup.mts.md +0 -311
@@ -4,12 +4,14 @@ import { clone, hash, getUUID } from '@ibgib/helper-gib/dist/helpers/utils-helpe
4
4
  import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
5
5
  import { GLOBAL_LOG_A_LOT } from '../core-constants.mjs';
6
6
  import { KeystoneStrategyFactory } from './strategy/keystone-strategy-factory.mjs';
7
+ import { KeystoneReplenishStrategy, KeystoneClaimType } from './keystone-types.mjs';
7
8
  import { createRevocationPoolConfig, createStandardPoolConfig } from './keystone-config-builder.mjs';
8
- import { POOL_ID_DEFAULT, POOL_ID_REVOKE, KEYSTONE_VERB_REVOKE, KEYSTONE_VERB_MANAGE, POOL_ID_MANAGE } from './keystone-constants.mjs';
9
+ import { POOL_ID_DEFAULT, POOL_ID_REVOKE, KEYSTONE_VERB_REVOKE, KEYSTONE_VERB_MANAGE, POOL_ID_MANAGE, KEYSTONE_VERB_SYNC, POOL_ID_CUSTODIAN_MANAGE } from './keystone-constants.mjs';
9
10
  import { KeystoneService_V1, } from './keystone-service-v1.mjs';
10
- import { selectChallengeIds, validateChallengePool, validateKeystoneIb, validateKeystoneTransition, validateGenesisKeystone } from './keystone-helpers.mjs';
11
+ import { selectChallengeIds, validateChallengePool, validateKeystoneIb, validateKeystoneTransition, validateGenesisKeystone, rotatePoolChallenges } from './keystone-helpers.mjs';
11
12
  import { KeystoneProfileBuilder } from './policy/keystone-profile-builder.mjs';
12
13
  import { getTjpAddr } from '../common/other/ibgib-helper.mjs';
14
+ import { deriveDelegateSecret } from './strategy/hash-reveal-v1/hash-reveal-v1.mjs';
13
15
  const logalot = GLOBAL_LOG_A_LOT;
14
16
  /**
15
17
  * A simple in-memory map acting as a Space.
@@ -181,6 +183,14 @@ await respecfully(sir, 'Suite A: Strategy Vectors (HashRevealV1)', async () => {
181
183
  const secretB = await strategyB.derivePoolSecret({ masterSecret });
182
184
  iReckon(sir, secretA).asTo('salt affects secret').not.willEqual(secretB);
183
185
  });
186
+ await ifWeMight(sir, 'deriveDelegateSecret derives a consistent secret using KDF', async () => {
187
+ const secretA = await deriveDelegateSecret({ masterSecret });
188
+ const secretB = await deriveDelegateSecret({ masterSecret });
189
+ iReckon(sir, secretA).asTo('consistency').willEqual(secretB);
190
+ iReckon(sir, secretA).asTo('truthy').isGonnaBeTruthy();
191
+ const secretC = await deriveDelegateSecret({ masterSecret: masterSecret + '_other' });
192
+ iReckon(sir, secretA).asTo('differ').not.willEqual(secretC);
193
+ });
184
194
  });
185
195
  await respecfully(sir, 'Challenge/Solution Logic', async () => {
186
196
  await ifWeMight(sir, 'generateSolution -> generateChallenge -> validateSolution loop works', async () => {
@@ -460,6 +470,42 @@ await respecfully(sir, 'Suite D: Revocation', async () => {
460
470
  const remaining = Object.keys(revokePool.challenges);
461
471
  iReckon(sir, remaining.length).asTo('pool depleted').willEqual(0);
462
472
  });
473
+ await ifWeMight(sir, 'fails to sign any new claims after revocation', async () => {
474
+ let errorCaught = false;
475
+ try {
476
+ await service.sign({
477
+ latestKeystone: revokedKeystone,
478
+ masterSecret,
479
+ claim: { verb: "post", target: "some_target" },
480
+ metaspace: mockMetaspace,
481
+ space: mockSpace,
482
+ });
483
+ }
484
+ catch (err) {
485
+ errorCaught = true;
486
+ iReckon(sir, err.message).includes("has been revoked");
487
+ }
488
+ iReckon(sir, errorCaught).asTo('signing blocked after revocation').isGonnaBeTrue();
489
+ });
490
+ await ifWeMight(sir, 'fails to evolve or add pools after revocation', async () => {
491
+ let errorCaught = false;
492
+ try {
493
+ const dummyConfig = createStandardPoolConfig({ id: "dummy", salt: "dummy" });
494
+ const dummyPool = { id: "dummy", config: dummyConfig, challenges: {} };
495
+ await service.addPools({
496
+ latestKeystone: revokedKeystone,
497
+ masterSecret,
498
+ newPools: [dummyPool],
499
+ metaspace: mockMetaspace,
500
+ space: mockSpace,
501
+ });
502
+ }
503
+ catch (err) {
504
+ errorCaught = true;
505
+ iReckon(sir, err.message).includes("has been revoked");
506
+ }
507
+ iReckon(sir, errorCaught).asTo('adding pools blocked after revocation').isGonnaBeTrue();
508
+ });
463
509
  });
464
510
  });
465
511
  // ===========================================================================
@@ -925,6 +971,16 @@ await respecfully(sir, 'Suite F: Deep Inspection', async () => {
925
971
  iReckon(sir, details.description).asTo('description').willEqual('Alice test keystone');
926
972
  iReckon(sir, details.extraInfo).asTo('extraInfo').willEqual('abc');
927
973
  });
974
+ await ifWeMight(sir, 'compiles configs successfully with domain profile (omitting connect)', async () => {
975
+ const builder = KeystoneProfileBuilder.buildKeystone('domain')
976
+ .withUsername('alice')
977
+ .withEmail('alice@example.com')
978
+ .withDescription('Alice domain keystone');
979
+ const configs = await builder.compileConfigs();
980
+ iReckon(sir, configs.length).asTo('number of pools').willEqual(4);
981
+ const connectPool = configs.find(c => c.id === 'connect');
982
+ iReckon(sir, connectPool).asTo('connect pool omitted').isGonnaBeUndefined();
983
+ });
928
984
  await ifWeMight(sir, 'supports customized overrides', async () => {
929
985
  const builder = KeystoneProfileBuilder.buildKeystone('test')
930
986
  .customizePool('sync', {
@@ -1046,6 +1102,7 @@ await respecfully(sir, 'Suite E: Delegation (Register/Unregister)', async () =>
1046
1102
  masterSecret: parentSecret,
1047
1103
  metaspace: mockMetaspace,
1048
1104
  space: mockSpace,
1105
+ allowedVerbs: [KEYSTONE_VERB_SYNC],
1049
1106
  });
1050
1107
  iReckon(sir, evolvedParent).asTo('evolved parent exists').isGonnaBeTruthy();
1051
1108
  iReckon(sir, evolvedParent.data?.delegates).asTo('delegates map exists').isGonnaBeTruthy();
@@ -1081,6 +1138,7 @@ await respecfully(sir, 'Suite E: Delegation (Register/Unregister)', async () =>
1081
1138
  masterSecret: parentSecret,
1082
1139
  metaspace: mockMetaspace,
1083
1140
  space: mockSpace,
1141
+ allowedVerbs: [KEYSTONE_VERB_SYNC],
1084
1142
  });
1085
1143
  // After registration
1086
1144
  const statusAfter = await service.getDelegateStatus({
@@ -1100,6 +1158,7 @@ await respecfully(sir, 'Suite E: Delegation (Register/Unregister)', async () =>
1100
1158
  masterSecret: parentSecret,
1101
1159
  metaspace: mockMetaspace,
1102
1160
  space: mockSpace,
1161
+ allowedVerbs: [KEYSTONE_VERB_SYNC],
1103
1162
  });
1104
1163
  const delegateTjpAddr = getTjpAddr({ ibGib: delegateKeystone });
1105
1164
  if (!delegateTjpAddr) {
@@ -1135,6 +1194,7 @@ await respecfully(sir, 'Suite E: Delegation (Register/Unregister)', async () =>
1135
1194
  masterSecret: parentSecret,
1136
1195
  metaspace: mockMetaspace,
1137
1196
  space: mockSpace,
1197
+ allowedVerbs: [KEYSTONE_VERB_SYNC],
1138
1198
  });
1139
1199
  }
1140
1200
  catch (e) {
@@ -1198,6 +1258,7 @@ await respecfully(sir, 'Suite G: Delegate & IB Validation', async () => {
1198
1258
  masterSecret: parentSecret,
1199
1259
  metaspace: mockMetaspace,
1200
1260
  space: mockSpace,
1261
+ allowedVerbs: [KEYSTONE_VERB_SYNC],
1201
1262
  });
1202
1263
  const errors = await validateKeystoneTransition({
1203
1264
  currentIbGib: evolvedParent,
@@ -1212,6 +1273,7 @@ await respecfully(sir, 'Suite G: Delegate & IB Validation', async () => {
1212
1273
  masterSecret: parentSecret,
1213
1274
  metaspace: mockMetaspace,
1214
1275
  space: mockSpace,
1276
+ allowedVerbs: [KEYSTONE_VERB_SYNC],
1215
1277
  });
1216
1278
  const tamperedParent = JSON.parse(JSON.stringify(evolvedParent));
1217
1279
  // Remove the manage verb proof by converting it to sign
@@ -1234,6 +1296,7 @@ await respecfully(sir, 'Suite G: Delegate & IB Validation', async () => {
1234
1296
  masterSecret: parentSecret,
1235
1297
  metaspace: mockMetaspace,
1236
1298
  space: mockSpace,
1299
+ allowedVerbs: [KEYSTONE_VERB_SYNC],
1237
1300
  });
1238
1301
  const tamperedParent = JSON.parse(JSON.stringify(evolvedParent));
1239
1302
  const delegateTjpAddr = getTjpAddr({ ibGib: delegateKeystone });
@@ -1257,6 +1320,7 @@ await respecfully(sir, 'Suite G: Delegate & IB Validation', async () => {
1257
1320
  masterSecret: parentSecret,
1258
1321
  metaspace: mockMetaspace,
1259
1322
  space: mockSpace,
1323
+ allowedVerbs: [KEYSTONE_VERB_SYNC],
1260
1324
  });
1261
1325
  const tamperedParent = JSON.parse(JSON.stringify(evolvedParent));
1262
1326
  const delegateTjpAddr = getTjpAddr({ ibGib: delegateKeystone });
@@ -1278,6 +1342,7 @@ await respecfully(sir, 'Suite G: Delegate & IB Validation', async () => {
1278
1342
  masterSecret: parentSecret,
1279
1343
  metaspace: mockMetaspace,
1280
1344
  space: mockSpace,
1345
+ allowedVerbs: [KEYSTONE_VERB_SYNC],
1281
1346
  });
1282
1347
  const tamperedParent = JSON.parse(JSON.stringify(evolvedParent));
1283
1348
  const delegateTjpAddr = getTjpAddr({ ibGib: delegateKeystone });
@@ -1293,4 +1358,384 @@ await respecfully(sir, 'Suite G: Delegate & IB Validation', async () => {
1293
1358
  iReckon(sir, errors.some(e => e.includes('not a valid historical address in the parent keystone timeline'))).isGonnaBeTrue();
1294
1359
  });
1295
1360
  });
1361
+ // ===========================================================================
1362
+ // SUITE G: COMBINED POOLS & PASSWORD ROTATION
1363
+ // ===========================================================================
1364
+ await respecfully(sir, 'Suite G: Combined Pools & Password Rotation', async () => {
1365
+ const service = new KeystoneService_V1();
1366
+ const userSecret = "UserMasterSecret_123";
1367
+ const custodianSecret = "CustodianSecret_456";
1368
+ let mockSpace;
1369
+ let mockMetaspace;
1370
+ firstOfEach(sir, async () => {
1371
+ mockSpace = new MockIbGibSpace();
1372
+ mockMetaspace = new MockMetaspaceService(mockSpace);
1373
+ });
1374
+ await ifWeMight(sir, 'revokes a keystone using a separate revocation pool', async () => {
1375
+ const stdSalt = "std_salt";
1376
+ const revokeSalt = "revoke_salt";
1377
+ const stdConfig = createStandardPoolConfig({ id: POOL_ID_DEFAULT, salt: stdSalt });
1378
+ const revokeConfig = createStandardPoolConfig({ id: POOL_ID_REVOKE, salt: revokeSalt });
1379
+ revokeConfig.allowedVerbs = [KEYSTONE_VERB_REVOKE];
1380
+ revokeConfig.behavior.replenish = KeystoneReplenishStrategy.deleteAll;
1381
+ const keystone = await service.genesis({
1382
+ masterSecret: userSecret,
1383
+ configs: [stdConfig, revokeConfig],
1384
+ metaspace: mockMetaspace,
1385
+ space: mockSpace,
1386
+ });
1387
+ const revoked = await service.revoke({
1388
+ latestKeystone: keystone,
1389
+ masterSecret: userSecret,
1390
+ metaspace: mockMetaspace,
1391
+ space: mockSpace,
1392
+ });
1393
+ iReckon(sir, revoked.data?.revocationInfo).asTo('revocationInfo present').isGonnaBeTruthy();
1394
+ const revokePool = revoked.data?.challengePools.find(p => p.id === POOL_ID_REVOKE);
1395
+ iReckon(sir, Object.keys(revokePool.challenges).length).asTo('revoke pool depleted').willEqual(0);
1396
+ });
1397
+ await ifWeMight(sir, 'revokes a keystone using a combined manage/revoke pool and empties it', async () => {
1398
+ const salt = "combined_salt";
1399
+ const manageConfig = createStandardPoolConfig({ id: POOL_ID_MANAGE, salt });
1400
+ manageConfig.allowedVerbs = [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE];
1401
+ const keystone = await service.genesis({
1402
+ masterSecret: userSecret,
1403
+ configs: [manageConfig],
1404
+ metaspace: mockMetaspace,
1405
+ space: mockSpace,
1406
+ });
1407
+ const revoked = await service.revoke({
1408
+ latestKeystone: keystone,
1409
+ masterSecret: userSecret,
1410
+ metaspace: mockMetaspace,
1411
+ space: mockSpace,
1412
+ });
1413
+ iReckon(sir, revoked.data?.revocationInfo).asTo('revocationInfo present').isGonnaBeTruthy();
1414
+ const managePool = revoked.data?.challengePools.find(p => p.id === POOL_ID_MANAGE);
1415
+ iReckon(sir, Object.keys(managePool.challenges).length).asTo('combined manage pool depleted').willEqual(0);
1416
+ });
1417
+ await ifWeMight(sir, 'rotates user password successfully when signed by the user manage pool', async () => {
1418
+ const salt = "manage_salt";
1419
+ const manageConfig = createStandardPoolConfig({ id: POOL_ID_MANAGE, salt });
1420
+ manageConfig.allowedVerbs = [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE];
1421
+ const keystone = await service.genesis({
1422
+ masterSecret: userSecret,
1423
+ configs: [manageConfig],
1424
+ metaspace: mockMetaspace,
1425
+ space: mockSpace,
1426
+ });
1427
+ const newSecret = "NewUserSecret_999";
1428
+ const rotated = await service.changePassword({
1429
+ latestKeystone: keystone,
1430
+ newMasterSecret: newSecret,
1431
+ signingSecret: userSecret,
1432
+ signingPoolId: POOL_ID_MANAGE,
1433
+ metaspace: mockMetaspace,
1434
+ space: mockSpace,
1435
+ });
1436
+ iReckon(sir, rotated).isGonnaBeTruthy();
1437
+ // 1. Try to sign using the new secret (should succeed)
1438
+ const claim = { verb: KEYSTONE_VERB_MANAGE, target: "test_target" };
1439
+ const signedNew = await service.sign({
1440
+ latestKeystone: rotated,
1441
+ masterSecret: newSecret,
1442
+ claim,
1443
+ poolId: POOL_ID_MANAGE,
1444
+ metaspace: mockMetaspace,
1445
+ space: mockSpace,
1446
+ });
1447
+ iReckon(sir, signedNew).isGonnaBeTruthy();
1448
+ // 2. Try to sign using the old secret (should fail)
1449
+ let errorCaught = false;
1450
+ try {
1451
+ await service.sign({
1452
+ latestKeystone: rotated,
1453
+ masterSecret: userSecret,
1454
+ claim,
1455
+ poolId: POOL_ID_MANAGE,
1456
+ metaspace: mockMetaspace,
1457
+ space: mockSpace,
1458
+ });
1459
+ }
1460
+ catch {
1461
+ errorCaught = true;
1462
+ }
1463
+ iReckon(sir, errorCaught).asTo('old secret signing rejected').isGonnaBeTrue();
1464
+ });
1465
+ await ifWeMight(sir, 'recovers user password successfully when signed by the custodian manage pool', async () => {
1466
+ const userSalt = "user_salt";
1467
+ const custodianSalt = "custodian_salt";
1468
+ const userManageConfig = createStandardPoolConfig({ id: POOL_ID_MANAGE, salt: userSalt });
1469
+ userManageConfig.allowedVerbs = [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE];
1470
+ const custodianManageConfig = createStandardPoolConfig({ id: POOL_ID_CUSTODIAN_MANAGE, salt: custodianSalt });
1471
+ custodianManageConfig.allowedVerbs = [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE];
1472
+ // We simulate constructing the custodian pool "offline" by the server
1473
+ const strategy = KeystoneStrategyFactory.create({ config: custodianManageConfig });
1474
+ const poolSecret = await strategy.derivePoolSecret({ masterSecret: custodianSecret });
1475
+ const challenges = {};
1476
+ for (let i = 0; i < custodianManageConfig.behavior.size; i++) {
1477
+ const raw = await hash({ s: `${custodianManageConfig.salt}${Date.now()}${i}` });
1478
+ const challengeId = raw.substring(0, 16);
1479
+ const solution = await strategy.generateSolution({ poolSecret, poolId: custodianManageConfig.id, challengeId });
1480
+ challenges[challengeId] = await strategy.generateChallenge({ solution });
1481
+ }
1482
+ const custodianPool = {
1483
+ id: POOL_ID_CUSTODIAN_MANAGE,
1484
+ config: custodianManageConfig,
1485
+ challenges,
1486
+ isForeign: true
1487
+ };
1488
+ const keystone = await service.genesis({
1489
+ masterSecret: userSecret,
1490
+ configs: [userManageConfig],
1491
+ metaspace: mockMetaspace,
1492
+ space: mockSpace,
1493
+ });
1494
+ // Add the custodian pool to the keystone
1495
+ const keystoneWithCustodian = await service.addPools({
1496
+ latestKeystone: keystone,
1497
+ masterSecret: userSecret,
1498
+ newPools: [custodianPool],
1499
+ metaspace: mockMetaspace,
1500
+ space: mockSpace,
1501
+ });
1502
+ // The user forgets their secret. Recover using custodianSecret!
1503
+ const recoveredSecret = "RecoveredUserSecret_777";
1504
+ const recoveredKeystone = await service.changePassword({
1505
+ latestKeystone: keystoneWithCustodian,
1506
+ newMasterSecret: recoveredSecret,
1507
+ signingSecret: custodianSecret,
1508
+ signingPoolId: POOL_ID_CUSTODIAN_MANAGE,
1509
+ metaspace: mockMetaspace,
1510
+ space: mockSpace,
1511
+ });
1512
+ iReckon(sir, recoveredKeystone).isGonnaBeTruthy();
1513
+ // Verify we can sign with recovered secret
1514
+ const claim = { verb: KEYSTONE_VERB_MANAGE, target: "test_target" };
1515
+ const signedNew = await service.sign({
1516
+ latestKeystone: recoveredKeystone,
1517
+ masterSecret: recoveredSecret,
1518
+ claim,
1519
+ poolId: POOL_ID_MANAGE,
1520
+ metaspace: mockMetaspace,
1521
+ space: mockSpace,
1522
+ });
1523
+ iReckon(sir, signedNew).isGonnaBeTruthy();
1524
+ });
1525
+ await ifWeMight(sir, 'rotates custodian manage pool successfully', async () => {
1526
+ const userSalt = "user_salt";
1527
+ const custodianSalt = "custodian_salt";
1528
+ const userManageConfig = createStandardPoolConfig({ id: POOL_ID_MANAGE, salt: userSalt });
1529
+ userManageConfig.allowedVerbs = [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE];
1530
+ const custodianManageConfig = createStandardPoolConfig({ id: POOL_ID_CUSTODIAN_MANAGE, salt: custodianSalt });
1531
+ custodianManageConfig.allowedVerbs = [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE];
1532
+ // Custodian Pool genesis
1533
+ const strategy = KeystoneStrategyFactory.create({ config: custodianManageConfig });
1534
+ const poolSecret = await strategy.derivePoolSecret({ masterSecret: custodianSecret });
1535
+ const challenges = {};
1536
+ for (let i = 0; i < custodianManageConfig.behavior.size; i++) {
1537
+ const raw = await hash({ s: `${custodianManageConfig.salt}${Date.now()}${i}` });
1538
+ const challengeId = raw.substring(0, 16);
1539
+ const solution = await strategy.generateSolution({ poolSecret, poolId: custodianManageConfig.id, challengeId });
1540
+ challenges[challengeId] = await strategy.generateChallenge({ solution });
1541
+ }
1542
+ const custodianPool = {
1543
+ id: POOL_ID_CUSTODIAN_MANAGE,
1544
+ config: custodianManageConfig,
1545
+ challenges,
1546
+ isForeign: true
1547
+ };
1548
+ const keystone = await service.genesis({
1549
+ masterSecret: userSecret,
1550
+ configs: [userManageConfig],
1551
+ metaspace: mockMetaspace,
1552
+ space: mockSpace,
1553
+ });
1554
+ const keystoneWithCustodian = await service.addPools({
1555
+ latestKeystone: keystone,
1556
+ masterSecret: userSecret,
1557
+ newPools: [custodianPool],
1558
+ metaspace: mockMetaspace,
1559
+ space: mockSpace,
1560
+ });
1561
+ // Rotate custodian credentials to new secret
1562
+ const newCustodianSecret = "NewCustodianSecret_888";
1563
+ const newCustodianConfig = createStandardPoolConfig({ id: POOL_ID_CUSTODIAN_MANAGE, salt: "new_custodian_salt" });
1564
+ newCustodianConfig.allowedVerbs = [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE];
1565
+ const newStrategy = KeystoneStrategyFactory.create({ config: newCustodianConfig });
1566
+ const newPoolSecret = await newStrategy.derivePoolSecret({ masterSecret: newCustodianSecret });
1567
+ const newChallenges = {};
1568
+ for (let i = 0; i < newCustodianConfig.behavior.size; i++) {
1569
+ const raw = await hash({ s: `${newCustodianConfig.salt}${Date.now()}${i}` });
1570
+ const challengeId = raw.substring(0, 16);
1571
+ const solution = await newStrategy.generateSolution({ poolSecret: newPoolSecret, poolId: newCustodianConfig.id, challengeId });
1572
+ newChallenges[challengeId] = await newStrategy.generateChallenge({ solution });
1573
+ }
1574
+ const newCustodianPool = {
1575
+ id: POOL_ID_CUSTODIAN_MANAGE,
1576
+ config: newCustodianConfig,
1577
+ challenges: newChallenges,
1578
+ isForeign: true
1579
+ };
1580
+ // Rotate signed by user manage pool
1581
+ const rotated = await service.replacePool({
1582
+ latestKeystone: keystoneWithCustodian,
1583
+ newPool: newCustodianPool,
1584
+ signingSecret: userSecret,
1585
+ signingPoolId: POOL_ID_MANAGE,
1586
+ metaspace: mockMetaspace,
1587
+ space: mockSpace,
1588
+ });
1589
+ iReckon(sir, rotated).isGonnaBeTruthy();
1590
+ // Verify we can sign with new custodian secret
1591
+ const claim = { verb: KEYSTONE_VERB_MANAGE, target: "test_target" };
1592
+ const signedNew = await service.sign({
1593
+ latestKeystone: rotated,
1594
+ masterSecret: newCustodianSecret,
1595
+ claim,
1596
+ poolId: POOL_ID_CUSTODIAN_MANAGE,
1597
+ metaspace: mockMetaspace,
1598
+ space: mockSpace,
1599
+ });
1600
+ iReckon(sir, signedNew).isGonnaBeTruthy();
1601
+ });
1602
+ await ifWeMight(sir, 'rotates all non-foreign pools during changePassword by default', async () => {
1603
+ const userSalt = "user_salt";
1604
+ const custodianSalt = "custodian_salt";
1605
+ const secondarySalt = "secondary_salt";
1606
+ const userManageConfig = createStandardPoolConfig({ id: POOL_ID_MANAGE, salt: userSalt });
1607
+ userManageConfig.allowedVerbs = [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE];
1608
+ // Another native/non-foreign pool
1609
+ const secondaryNativeConfig = createStandardPoolConfig({ id: "secondary-native", salt: secondarySalt });
1610
+ secondaryNativeConfig.allowedVerbs = [KEYSTONE_VERB_SYNC];
1611
+ // A custodian/foreign pool
1612
+ const custodianManageConfig = createStandardPoolConfig({ id: POOL_ID_CUSTODIAN_MANAGE, salt: custodianSalt });
1613
+ custodianManageConfig.allowedVerbs = [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE];
1614
+ // Custodian Pool genesis
1615
+ const strategy = KeystoneStrategyFactory.create({ config: custodianManageConfig });
1616
+ const poolSecret = await strategy.derivePoolSecret({ masterSecret: custodianSecret });
1617
+ const challenges = {};
1618
+ for (let i = 0; i < custodianManageConfig.behavior.size; i++) {
1619
+ const raw = await hash({ s: `${custodianManageConfig.salt}${Date.now()}${i}` });
1620
+ const challengeId = raw.substring(0, 16);
1621
+ const solution = await strategy.generateSolution({ poolSecret, poolId: custodianManageConfig.id, challengeId });
1622
+ challenges[challengeId] = await strategy.generateChallenge({ solution });
1623
+ }
1624
+ const custodianPool = {
1625
+ id: POOL_ID_CUSTODIAN_MANAGE,
1626
+ config: custodianManageConfig,
1627
+ challenges,
1628
+ isForeign: true
1629
+ };
1630
+ const keystone = await service.genesis({
1631
+ masterSecret: userSecret,
1632
+ configs: [userManageConfig, secondaryNativeConfig],
1633
+ metaspace: mockMetaspace,
1634
+ space: mockSpace,
1635
+ });
1636
+ const keystoneWithCustodian = await service.addPools({
1637
+ latestKeystone: keystone,
1638
+ masterSecret: userSecret,
1639
+ newPools: [custodianPool],
1640
+ metaspace: mockMetaspace,
1641
+ space: mockSpace,
1642
+ });
1643
+ // Rotate passphrase/password
1644
+ const newPassphrase = "SuperNewSecretPassphrase_999";
1645
+ const rotated = await service.changePassword({
1646
+ latestKeystone: keystoneWithCustodian,
1647
+ newMasterSecret: newPassphrase,
1648
+ signingSecret: userSecret,
1649
+ signingPoolId: POOL_ID_MANAGE,
1650
+ metaspace: mockMetaspace,
1651
+ space: mockSpace,
1652
+ });
1653
+ iReckon(sir, rotated).isGonnaBeTruthy();
1654
+ const data = rotated.data;
1655
+ const rotatedManage = data.challengePools.find(p => p.id === POOL_ID_MANAGE);
1656
+ const rotatedSecondary = data.challengePools.find(p => p.id === "secondary-native");
1657
+ const rotatedCustodian = data.challengePools.find(p => p.id === POOL_ID_CUSTODIAN_MANAGE);
1658
+ // Verify native pools rotated (salt changed)
1659
+ iReckon(sir, rotatedManage.config.salt).not.willEqual(userSalt);
1660
+ iReckon(sir, rotatedSecondary.config.salt).not.willEqual(secondarySalt);
1661
+ // Verify foreign pool was not modified (salt identical)
1662
+ iReckon(sir, rotatedCustodian.config.salt).willEqual(custodianSalt);
1663
+ // Verify we can validate transition
1664
+ const errors = await service.validate({
1665
+ prevIbGib: keystoneWithCustodian,
1666
+ currentIbGib: rotated
1667
+ });
1668
+ iReckon(sir, errors.length).willEqual(0);
1669
+ });
1670
+ await ifWeMight(sir, 'fails validation if password rotation details or constraints are violated', async () => {
1671
+ // Construct invalid change-password transition
1672
+ const userManageConfig = createStandardPoolConfig({ id: POOL_ID_MANAGE, salt: "salt1" });
1673
+ userManageConfig.allowedVerbs = [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE];
1674
+ const custodianManageConfig = createStandardPoolConfig({ id: POOL_ID_CUSTODIAN_MANAGE, salt: "salt2" });
1675
+ custodianManageConfig.allowedVerbs = [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE];
1676
+ // Custodian Pool genesis
1677
+ const strategy = KeystoneStrategyFactory.create({ config: custodianManageConfig });
1678
+ const poolSecret = await strategy.derivePoolSecret({ masterSecret: custodianSecret });
1679
+ const challenges = {};
1680
+ for (let i = 0; i < custodianManageConfig.behavior.size; i++) {
1681
+ const raw = await hash({ s: `${custodianManageConfig.salt}${Date.now()}${i}` });
1682
+ const challengeId = raw.substring(0, 16);
1683
+ const solution = await strategy.generateSolution({ poolSecret, poolId: custodianManageConfig.id, challengeId });
1684
+ challenges[challengeId] = await strategy.generateChallenge({ solution });
1685
+ }
1686
+ const custodianPool = {
1687
+ id: POOL_ID_CUSTODIAN_MANAGE,
1688
+ config: custodianManageConfig,
1689
+ challenges,
1690
+ isForeign: true
1691
+ };
1692
+ const keystone = await service.genesis({
1693
+ masterSecret: userSecret,
1694
+ configs: [userManageConfig],
1695
+ metaspace: mockMetaspace,
1696
+ space: mockSpace,
1697
+ });
1698
+ const keystoneWithCustodian = await service.addPools({
1699
+ latestKeystone: keystone,
1700
+ masterSecret: userSecret,
1701
+ newPools: [custodianPool],
1702
+ metaspace: mockMetaspace,
1703
+ space: mockSpace,
1704
+ });
1705
+ // Create an invalid frame manually where a foreign pool is rotated during change-password
1706
+ const badRotatedPool = await rotatePoolChallenges({
1707
+ prevPool: custodianPool,
1708
+ newMasterSecret: "bad_secret"
1709
+ });
1710
+ const evolved = await service.changePassword({
1711
+ latestKeystone: keystoneWithCustodian,
1712
+ newMasterSecret: "new_pass",
1713
+ signingSecret: userSecret,
1714
+ signingPoolId: POOL_ID_MANAGE,
1715
+ metaspace: mockMetaspace,
1716
+ space: mockSpace,
1717
+ });
1718
+ // Inject the foreign pool rotation manually to simulate an attacker
1719
+ const attackerData = {
1720
+ ...evolved.data,
1721
+ challengePools: evolved.data.challengePools.map(p => p.id === POOL_ID_CUSTODIAN_MANAGE ? badRotatedPool : p)
1722
+ };
1723
+ // Update details claim to say we rotated custodian-manage
1724
+ attackerData.proofs[0].claim.details = {
1725
+ type: KeystoneClaimType.change_password,
1726
+ info: { change: [POOL_ID_MANAGE, POOL_ID_CUSTODIAN_MANAGE] }
1727
+ };
1728
+ const attackerKeystone = {
1729
+ ...evolved,
1730
+ data: attackerData
1731
+ };
1732
+ const errors = await service.validate({
1733
+ prevIbGib: keystoneWithCustodian,
1734
+ currentIbGib: attackerKeystone
1735
+ });
1736
+ iReckon(sir, errors.length > 0).isGonnaBeTrue();
1737
+ const hasForeignViolation = errors.some(e => e.includes("Security Violation") && e.includes("foreign pool"));
1738
+ iReckon(sir, hasForeignViolation).isGonnaBeTrue();
1739
+ });
1740
+ });
1296
1741
  //# sourceMappingURL=keystone-service-v1.respec.mjs.map