@ibgib/core-gib 0.1.64 → 0.1.66

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 (32) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/keystone/keystone-helpers.d.mts +8 -0
  3. package/dist/keystone/keystone-helpers.d.mts.map +1 -1
  4. package/dist/keystone/keystone-helpers.mjs +74 -4
  5. package/dist/keystone/keystone-helpers.mjs.map +1 -1
  6. package/dist/keystone/keystone-service-v1.d.mts +85 -18
  7. package/dist/keystone/keystone-service-v1.d.mts.map +1 -1
  8. package/dist/keystone/keystone-service-v1.mjs +178 -22
  9. package/dist/keystone/keystone-service-v1.mjs.map +1 -1
  10. package/dist/keystone/keystone-service-v1.respec.mjs +780 -340
  11. package/dist/keystone/keystone-service-v1.respec.mjs.map +1 -1
  12. package/dist/keystone/keystone-types.d.mts +15 -2
  13. package/dist/keystone/keystone-types.d.mts.map +1 -1
  14. package/dist/keystone/keystone-types.mjs +2 -0
  15. package/dist/keystone/keystone-types.mjs.map +1 -1
  16. package/dist/keystone/policy/profiles/profile-domain.json +2 -46
  17. package/dist/keystone/policy/profiles/profile-sync.json +2 -16
  18. package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-receiver/sync-websocket-peer-helpers.d.mts +27 -4
  19. package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-receiver/sync-websocket-peer-helpers.d.mts.map +1 -1
  20. package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-receiver/sync-websocket-peer-helpers.mjs +230 -135
  21. package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-receiver/sync-websocket-peer-helpers.mjs.map +1 -1
  22. package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.mjs +1 -1
  23. package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.mjs.map +1 -1
  24. package/package.json +1 -1
  25. package/src/keystone/keystone-helpers.mts +89 -4
  26. package/src/keystone/keystone-service-v1.mts +262 -34
  27. package/src/keystone/keystone-service-v1.respec.mts +792 -376
  28. package/src/keystone/keystone-types.mts +16 -1
  29. package/src/keystone/policy/profiles/profile-domain.json +2 -46
  30. package/src/keystone/policy/profiles/profile-sync.json +2 -16
  31. package/src/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-receiver/sync-websocket-peer-helpers.mts +294 -131
  32. package/src/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.mts +1 -1
@@ -1,6 +1,6 @@
1
1
  import { respecfully, iReckon, firstOfAll, firstOfEach, ifWeMight } from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
2
2
  const maam = `[${import.meta.url}]`, sir = maam;
3
- import { clone, hash, getUUID } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
3
+ import { clone, hash, getUUID, extractErrorMsg } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
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';
@@ -143,6 +143,33 @@ class MockMetaspaceService {
143
143
  const addr = getIbGibAddr({ ibGib });
144
144
  this.timelineHeads.set(tjpGib, addr);
145
145
  }
146
+ /**
147
+ * Resolves the latest address for a timeline.
148
+ */
149
+ async getLatestAddr({ addr, tjpAddr, space }) {
150
+ const targetAddr = tjpAddr || addr;
151
+ if (!targetAddr)
152
+ return undefined;
153
+ const targetSpace = space || this.space;
154
+ const gib = targetAddr.split('^')[1] || '';
155
+ let tjpGib = gib.includes('.') ? gib.split('.').slice(1).join('.') : gib;
156
+ if (this.timelineHeads.has(tjpGib)) {
157
+ return this.timelineHeads.get(tjpGib);
158
+ }
159
+ if (targetSpace && typeof targetSpace.witness === 'function') {
160
+ const res = await targetSpace.witness({
161
+ data: {
162
+ cmd: 'get',
163
+ cmdModifiers: ['latest', 'addrs'],
164
+ ibGibAddrs: [targetAddr]
165
+ }
166
+ });
167
+ if (res?.data?.success && res?.data?.latestAddrsMap?.[targetAddr]) {
168
+ return res.data.latestAddrsMap[targetAddr];
169
+ }
170
+ }
171
+ return targetAddr;
172
+ }
146
173
  }
147
174
  // ===========================================================================
148
175
  // SUITE A: STRATEGY VECTORS (The Math)
@@ -977,7 +1004,7 @@ await respecfully(sir, 'Suite F: Deep Inspection', async () => {
977
1004
  .withEmail('alice@example.com')
978
1005
  .withDescription('Alice domain keystone');
979
1006
  const configs = await builder.compileConfigs();
980
- iReckon(sir, configs.length).asTo('number of pools').willEqual(4);
1007
+ iReckon(sir, configs.length).asTo('number of pools').willEqual(1);
981
1008
  const connectPool = configs.find(c => c.id === 'connect');
982
1009
  iReckon(sir, connectPool).asTo('connect pool omitted').isGonnaBeUndefined();
983
1010
  });
@@ -1372,370 +1399,783 @@ await respecfully(sir, 'Suite G: Combined Pools & Password Rotation', async () =
1372
1399
  mockMetaspace = new MockMetaspaceService(mockSpace);
1373
1400
  });
1374
1401
  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);
1402
+ const lc = `[revokes a keystone using a separate revocation pool]`;
1403
+ try {
1404
+ if (logalot) {
1405
+ console.log(`${lc} starting... (I: 8efbb3af6a483aa4c1205118bd481926)`);
1406
+ }
1407
+ const stdSalt = "std_salt";
1408
+ const revokeSalt = "revoke_salt";
1409
+ const stdConfig = createStandardPoolConfig({ id: POOL_ID_DEFAULT, salt: stdSalt });
1410
+ const revokeConfig = createStandardPoolConfig({ id: POOL_ID_REVOKE, salt: revokeSalt });
1411
+ revokeConfig.allowedVerbs = [KEYSTONE_VERB_REVOKE];
1412
+ revokeConfig.behavior.replenish = KeystoneReplenishStrategy.deleteAll;
1413
+ const keystone = await service.genesis({
1414
+ masterSecret: userSecret,
1415
+ configs: [stdConfig, revokeConfig],
1416
+ metaspace: mockMetaspace,
1417
+ space: mockSpace,
1418
+ });
1419
+ const revoked = await service.revoke({
1420
+ latestKeystone: keystone,
1421
+ masterSecret: userSecret,
1422
+ metaspace: mockMetaspace,
1423
+ space: mockSpace,
1424
+ });
1425
+ iReckon(sir, revoked.data?.revocationInfo).asTo('revocationInfo present').isGonnaBeTruthy();
1426
+ const revokePool = revoked.data?.challengePools.find(p => p.id === POOL_ID_REVOKE);
1427
+ iReckon(sir, Object.keys(revokePool.challenges).length).asTo('revoke pool depleted').willEqual(0);
1428
+ }
1429
+ catch (error) {
1430
+ console.error(`${lc} ${extractErrorMsg(error)}`);
1431
+ throw error;
1432
+ }
1433
+ finally {
1434
+ if (logalot) {
1435
+ console.log(`${lc} complete.`);
1436
+ }
1437
+ }
1396
1438
  });
1397
1439
  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);
1440
+ const lc = `[revokes a keystone using a combined manage/revoke pool and empties it]`;
1441
+ try {
1442
+ if (logalot) {
1443
+ console.log(`${lc} starting... (I: 31d8bc860b7cb09893ccaac3d2cf4826)`);
1444
+ }
1445
+ const salt = "combined_salt";
1446
+ const manageConfig = createStandardPoolConfig({ id: POOL_ID_MANAGE, salt });
1447
+ manageConfig.allowedVerbs = [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE];
1448
+ const keystone = await service.genesis({
1449
+ masterSecret: userSecret,
1450
+ configs: [manageConfig],
1451
+ metaspace: mockMetaspace,
1452
+ space: mockSpace,
1453
+ });
1454
+ const revoked = await service.revoke({
1455
+ latestKeystone: keystone,
1456
+ masterSecret: userSecret,
1457
+ metaspace: mockMetaspace,
1458
+ space: mockSpace,
1459
+ });
1460
+ iReckon(sir, revoked.data?.revocationInfo).asTo('revocationInfo present').isGonnaBeTruthy();
1461
+ const managePool = revoked.data?.challengePools.find(p => p.id === POOL_ID_MANAGE);
1462
+ iReckon(sir, Object.keys(managePool.challenges).length).asTo('combined manage pool depleted').willEqual(0);
1463
+ }
1464
+ catch (error) {
1465
+ console.error(`${lc} ${extractErrorMsg(error)}`);
1466
+ throw error;
1467
+ }
1468
+ finally {
1469
+ if (logalot) {
1470
+ console.log(`${lc} complete.`);
1471
+ }
1472
+ }
1416
1473
  });
1417
1474
  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;
1475
+ const lc = `[rotates user password successfully when signed by the user manage pool]`;
1450
1476
  try {
1451
- await service.sign({
1452
- latestKeystone: rotated,
1477
+ if (logalot) {
1478
+ console.log(`${lc} starting... (I: 580b0bbdc7f80e35d8ee2339ac0eb526)`);
1479
+ }
1480
+ const salt = "manage_salt";
1481
+ const manageConfig = createStandardPoolConfig({ id: POOL_ID_MANAGE, salt });
1482
+ manageConfig.allowedVerbs = [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE];
1483
+ const keystone = await service.genesis({
1453
1484
  masterSecret: userSecret,
1485
+ configs: [manageConfig],
1486
+ metaspace: mockMetaspace,
1487
+ space: mockSpace,
1488
+ });
1489
+ const newSecret = "NewUserSecret_999";
1490
+ const { newKeystone: rotated } = await service.changePassword({
1491
+ keystoneIbGib: keystone,
1492
+ newMasterSecret: newSecret,
1493
+ signingSecret: userSecret,
1494
+ signingPoolId: POOL_ID_MANAGE,
1495
+ metaspace: mockMetaspace,
1496
+ space: mockSpace,
1497
+ });
1498
+ iReckon(sir, rotated).isGonnaBeTruthy();
1499
+ // 1. Try to sign using the new secret (should succeed)
1500
+ const claim = { verb: KEYSTONE_VERB_MANAGE, target: "test_target" };
1501
+ const signedNew = await service.sign({
1502
+ latestKeystone: rotated,
1503
+ masterSecret: newSecret,
1454
1504
  claim,
1455
1505
  poolId: POOL_ID_MANAGE,
1456
1506
  metaspace: mockMetaspace,
1457
1507
  space: mockSpace,
1458
1508
  });
1509
+ iReckon(sir, signedNew).isGonnaBeTruthy();
1510
+ // 2. Try to sign using the old secret (should fail)
1511
+ let errorCaught = false;
1512
+ try {
1513
+ await service.sign({
1514
+ latestKeystone: rotated,
1515
+ masterSecret: userSecret,
1516
+ claim,
1517
+ poolId: POOL_ID_MANAGE,
1518
+ metaspace: mockMetaspace,
1519
+ space: mockSpace,
1520
+ });
1521
+ }
1522
+ catch {
1523
+ errorCaught = true;
1524
+ }
1525
+ iReckon(sir, errorCaught).asTo('old secret signing rejected').isGonnaBeTrue();
1459
1526
  }
1460
- catch {
1461
- errorCaught = true;
1527
+ catch (error) {
1528
+ console.error(`${lc} ${extractErrorMsg(error)}`);
1529
+ throw error;
1530
+ }
1531
+ finally {
1532
+ if (logalot) {
1533
+ console.log(`${lc} complete.`);
1534
+ }
1462
1535
  }
1463
- iReckon(sir, errorCaught).asTo('old secret signing rejected').isGonnaBeTrue();
1464
1536
  });
1465
1537
  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 });
1538
+ const lc = `[recovers user password successfully when signed by the custodian manage pool]`;
1539
+ try {
1540
+ if (logalot) {
1541
+ console.log(`${lc} starting... (I: 409df8ee1ae81d8b45c4a9c98e6e7626)`);
1542
+ }
1543
+ const userSalt = "user_salt";
1544
+ const custodianSalt = "custodian_salt";
1545
+ const userManageConfig = createStandardPoolConfig({ id: POOL_ID_MANAGE, salt: userSalt });
1546
+ userManageConfig.allowedVerbs = [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE];
1547
+ const custodianManageConfig = createStandardPoolConfig({ id: POOL_ID_CUSTODIAN_MANAGE, salt: custodianSalt });
1548
+ custodianManageConfig.allowedVerbs = [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE];
1549
+ // We simulate constructing the custodian pool "offline" by the server
1550
+ const strategy = KeystoneStrategyFactory.create({ config: custodianManageConfig });
1551
+ const poolSecret = await strategy.derivePoolSecret({ masterSecret: custodianSecret });
1552
+ const challenges = {};
1553
+ for (let i = 0; i < custodianManageConfig.behavior.size; i++) {
1554
+ const raw = await hash({ s: `${custodianManageConfig.salt}${Date.now()}${i}` });
1555
+ const challengeId = raw.substring(0, 16);
1556
+ const solution = await strategy.generateSolution({ poolSecret, poolId: custodianManageConfig.id, challengeId });
1557
+ challenges[challengeId] = await strategy.generateChallenge({ solution });
1558
+ }
1559
+ const custodianPool = {
1560
+ id: POOL_ID_CUSTODIAN_MANAGE,
1561
+ config: custodianManageConfig,
1562
+ challenges,
1563
+ isForeign: true
1564
+ };
1565
+ const keystone = await service.genesis({
1566
+ masterSecret: userSecret,
1567
+ configs: [userManageConfig],
1568
+ metaspace: mockMetaspace,
1569
+ space: mockSpace,
1570
+ });
1571
+ // Add the custodian pool to the keystone
1572
+ const keystoneWithCustodian = await service.addPools({
1573
+ latestKeystone: keystone,
1574
+ masterSecret: userSecret,
1575
+ newPools: [custodianPool],
1576
+ metaspace: mockMetaspace,
1577
+ space: mockSpace,
1578
+ });
1579
+ // The user forgets their secret. Recover using custodianSecret!
1580
+ const recoveredSecret = "RecoveredUserSecret_777";
1581
+ const { newKeystone: recoveredKeystone } = await service.changePassword({
1582
+ keystoneIbGib: keystoneWithCustodian,
1583
+ newMasterSecret: recoveredSecret,
1584
+ signingSecret: custodianSecret,
1585
+ signingPoolId: POOL_ID_CUSTODIAN_MANAGE,
1586
+ metaspace: mockMetaspace,
1587
+ space: mockSpace,
1588
+ });
1589
+ iReckon(sir, recoveredKeystone).isGonnaBeTruthy();
1590
+ // Verify we can sign with recovered secret
1591
+ const claim = { verb: KEYSTONE_VERB_MANAGE, target: "test_target" };
1592
+ const signedNew = await service.sign({
1593
+ latestKeystone: recoveredKeystone,
1594
+ masterSecret: recoveredSecret,
1595
+ claim,
1596
+ poolId: POOL_ID_MANAGE,
1597
+ metaspace: mockMetaspace,
1598
+ space: mockSpace,
1599
+ });
1600
+ iReckon(sir, signedNew).isGonnaBeTruthy();
1601
+ }
1602
+ catch (error) {
1603
+ console.error(`${lc} ${extractErrorMsg(error)}`);
1604
+ throw error;
1605
+ }
1606
+ finally {
1607
+ if (logalot) {
1608
+ console.log(`${lc} complete.`);
1609
+ }
1481
1610
  }
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
1611
  });
1525
1612
  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 });
1613
+ const lc = `[rotates custodian manage pool successfully]`;
1614
+ try {
1615
+ if (logalot) {
1616
+ console.log(`${lc} starting... (I: 76d9a76dc2487949d7deb31696d3e126)`);
1617
+ }
1618
+ const userSalt = "user_salt";
1619
+ const custodianSalt = "custodian_salt";
1620
+ const userManageConfig = createStandardPoolConfig({ id: POOL_ID_MANAGE, salt: userSalt });
1621
+ userManageConfig.allowedVerbs = [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE];
1622
+ const custodianManageConfig = createStandardPoolConfig({ id: POOL_ID_CUSTODIAN_MANAGE, salt: custodianSalt });
1623
+ custodianManageConfig.allowedVerbs = [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE];
1624
+ // Custodian Pool genesis
1625
+ const strategy = KeystoneStrategyFactory.create({ config: custodianManageConfig });
1626
+ const poolSecret = await strategy.derivePoolSecret({ masterSecret: custodianSecret });
1627
+ const challenges = {};
1628
+ for (let i = 0; i < custodianManageConfig.behavior.size; i++) {
1629
+ const raw = await hash({ s: `${custodianManageConfig.salt}${Date.now()}${i}` });
1630
+ const challengeId = raw.substring(0, 16);
1631
+ const solution = await strategy.generateSolution({ poolSecret, poolId: custodianManageConfig.id, challengeId });
1632
+ challenges[challengeId] = await strategy.generateChallenge({ solution });
1633
+ }
1634
+ const custodianPool = {
1635
+ id: POOL_ID_CUSTODIAN_MANAGE,
1636
+ config: custodianManageConfig,
1637
+ challenges,
1638
+ isForeign: true
1639
+ };
1640
+ const keystone = await service.genesis({
1641
+ masterSecret: userSecret,
1642
+ configs: [userManageConfig],
1643
+ metaspace: mockMetaspace,
1644
+ space: mockSpace,
1645
+ });
1646
+ const keystoneWithCustodian = await service.addPools({
1647
+ latestKeystone: keystone,
1648
+ masterSecret: userSecret,
1649
+ newPools: [custodianPool],
1650
+ metaspace: mockMetaspace,
1651
+ space: mockSpace,
1652
+ });
1653
+ // Rotate custodian credentials to new secret
1654
+ const newCustodianSecret = "NewCustodianSecret_888";
1655
+ const newCustodianConfig = createStandardPoolConfig({ id: POOL_ID_CUSTODIAN_MANAGE, salt: "new_custodian_salt" });
1656
+ newCustodianConfig.allowedVerbs = [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE];
1657
+ const newStrategy = KeystoneStrategyFactory.create({ config: newCustodianConfig });
1658
+ const newPoolSecret = await newStrategy.derivePoolSecret({ masterSecret: newCustodianSecret });
1659
+ const newChallenges = {};
1660
+ for (let i = 0; i < newCustodianConfig.behavior.size; i++) {
1661
+ const raw = await hash({ s: `${newCustodianConfig.salt}${Date.now()}${i}` });
1662
+ const challengeId = raw.substring(0, 16);
1663
+ const solution = await newStrategy.generateSolution({ poolSecret: newPoolSecret, poolId: newCustodianConfig.id, challengeId });
1664
+ newChallenges[challengeId] = await newStrategy.generateChallenge({ solution });
1665
+ }
1666
+ const newCustodianPool = {
1667
+ id: POOL_ID_CUSTODIAN_MANAGE,
1668
+ config: newCustodianConfig,
1669
+ challenges: newChallenges,
1670
+ isForeign: true
1671
+ };
1672
+ // Rotate signed by user manage pool
1673
+ const rotated = await service.replacePool({
1674
+ latestKeystone: keystoneWithCustodian,
1675
+ newPool: newCustodianPool,
1676
+ signingSecret: userSecret,
1677
+ signingPoolId: POOL_ID_MANAGE,
1678
+ metaspace: mockMetaspace,
1679
+ space: mockSpace,
1680
+ });
1681
+ iReckon(sir, rotated).isGonnaBeTruthy();
1682
+ // Verify we can sign with new custodian secret
1683
+ const claim = { verb: KEYSTONE_VERB_MANAGE, target: "test_target" };
1684
+ const signedNew = await service.sign({
1685
+ latestKeystone: rotated,
1686
+ masterSecret: newCustodianSecret,
1687
+ claim,
1688
+ poolId: POOL_ID_CUSTODIAN_MANAGE,
1689
+ metaspace: mockMetaspace,
1690
+ space: mockSpace,
1691
+ });
1692
+ iReckon(sir, signedNew).isGonnaBeTruthy();
1541
1693
  }
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 });
1694
+ catch (error) {
1695
+ console.error(`${lc} ${extractErrorMsg(error)}`);
1696
+ throw error;
1697
+ }
1698
+ finally {
1699
+ if (logalot) {
1700
+ console.log(`${lc} complete.`);
1701
+ }
1573
1702
  }
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
1703
  });
1602
1704
  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 });
1705
+ const lc = `[rotates all non-foreign pools during changePassword by default]`;
1706
+ try {
1707
+ if (logalot) {
1708
+ console.log(`${lc} starting... (I: 8449682eda6389e9d514888f9ce86f26)`);
1709
+ }
1710
+ const userSalt = "user_salt";
1711
+ const custodianSalt = "custodian_salt";
1712
+ const secondarySalt = "secondary_salt";
1713
+ const userManageConfig = createStandardPoolConfig({ id: POOL_ID_MANAGE, salt: userSalt });
1714
+ userManageConfig.allowedVerbs = [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE];
1715
+ // Another native/non-foreign pool
1716
+ const secondaryNativeConfig = createStandardPoolConfig({ id: "secondary-native", salt: secondarySalt });
1717
+ secondaryNativeConfig.allowedVerbs = [KEYSTONE_VERB_SYNC];
1718
+ // A custodian/foreign pool
1719
+ const custodianManageConfig = createStandardPoolConfig({ id: POOL_ID_CUSTODIAN_MANAGE, salt: custodianSalt });
1720
+ custodianManageConfig.allowedVerbs = [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE];
1721
+ // Custodian Pool genesis
1722
+ const strategy = KeystoneStrategyFactory.create({ config: custodianManageConfig });
1723
+ const poolSecret = await strategy.derivePoolSecret({ masterSecret: custodianSecret });
1724
+ const challenges = {};
1725
+ for (let i = 0; i < custodianManageConfig.behavior.size; i++) {
1726
+ const raw = await hash({ s: `${custodianManageConfig.salt}${Date.now()}${i}` });
1727
+ const challengeId = raw.substring(0, 16);
1728
+ const solution = await strategy.generateSolution({ poolSecret, poolId: custodianManageConfig.id, challengeId });
1729
+ challenges[challengeId] = await strategy.generateChallenge({ solution });
1730
+ }
1731
+ const custodianPool = {
1732
+ id: POOL_ID_CUSTODIAN_MANAGE,
1733
+ config: custodianManageConfig,
1734
+ challenges,
1735
+ isForeign: true
1736
+ };
1737
+ const keystone = await service.genesis({
1738
+ masterSecret: userSecret,
1739
+ configs: [userManageConfig, secondaryNativeConfig],
1740
+ metaspace: mockMetaspace,
1741
+ space: mockSpace,
1742
+ });
1743
+ const keystoneWithCustodian = await service.addPools({
1744
+ latestKeystone: keystone,
1745
+ masterSecret: userSecret,
1746
+ newPools: [custodianPool],
1747
+ metaspace: mockMetaspace,
1748
+ space: mockSpace,
1749
+ });
1750
+ // Rotate passphrase/password
1751
+ const newPassphrase = "SuperNewSecretPassphrase_999";
1752
+ const { newKeystone: rotated } = await service.changePassword({
1753
+ keystoneIbGib: keystoneWithCustodian,
1754
+ newMasterSecret: newPassphrase,
1755
+ signingSecret: userSecret,
1756
+ signingPoolId: POOL_ID_MANAGE,
1757
+ metaspace: mockMetaspace,
1758
+ space: mockSpace,
1759
+ });
1760
+ iReckon(sir, rotated).isGonnaBeTruthy();
1761
+ const data = rotated.data;
1762
+ const rotatedManage = data.challengePools.find(p => p.id === POOL_ID_MANAGE);
1763
+ const rotatedSecondary = data.challengePools.find(p => p.id === "secondary-native");
1764
+ const rotatedCustodian = data.challengePools.find(p => p.id === POOL_ID_CUSTODIAN_MANAGE);
1765
+ // Verify native pools rotated (salt changed)
1766
+ iReckon(sir, rotatedManage.config.salt).not.willEqual(userSalt);
1767
+ iReckon(sir, rotatedSecondary.config.salt).not.willEqual(secondarySalt);
1768
+ // Verify foreign pool was not modified (salt identical)
1769
+ iReckon(sir, rotatedCustodian.config.salt).willEqual(custodianSalt);
1770
+ // Verify we can validate transition
1771
+ const errors = await service.validate({
1772
+ prevIbGib: keystoneWithCustodian,
1773
+ currentIbGib: rotated
1774
+ });
1775
+ iReckon(sir, errors.length).willEqual(0);
1776
+ // Verify we can sign using the rotated 'manage' pool with the new passphrase
1777
+ const manageClaim = { verb: KEYSTONE_VERB_MANAGE, target: "test_target_manage" };
1778
+ const signedWithNewManage = await service.sign({
1779
+ latestKeystone: rotated,
1780
+ masterSecret: newPassphrase,
1781
+ claim: manageClaim,
1782
+ poolId: POOL_ID_MANAGE,
1783
+ metaspace: mockMetaspace,
1784
+ space: mockSpace,
1785
+ });
1786
+ iReckon(sir, signedWithNewManage).isGonnaBeTruthy();
1787
+ // Verify we can sign using the rotated 'secondary-native' pool with the new passphrase
1788
+ const syncClaim = { verb: KEYSTONE_VERB_SYNC, target: "test_target_sync" };
1789
+ const signedWithNewSync = await service.sign({
1790
+ latestKeystone: rotated,
1791
+ masterSecret: newPassphrase,
1792
+ claim: syncClaim,
1793
+ poolId: "secondary-native",
1794
+ metaspace: mockMetaspace,
1795
+ space: mockSpace,
1796
+ });
1797
+ iReckon(sir, signedWithNewSync).isGonnaBeTruthy();
1798
+ // Verify signing with the OLD master secret fails on both pools
1799
+ let oldManageFailed = false;
1800
+ try {
1801
+ await service.sign({
1802
+ latestKeystone: rotated,
1803
+ masterSecret: userSecret,
1804
+ claim: manageClaim,
1805
+ poolId: POOL_ID_MANAGE,
1806
+ metaspace: mockMetaspace,
1807
+ space: mockSpace,
1808
+ });
1809
+ }
1810
+ catch {
1811
+ oldManageFailed = true;
1812
+ }
1813
+ iReckon(sir, oldManageFailed).isGonnaBeTrue();
1814
+ let oldSyncFailed = false;
1815
+ try {
1816
+ await service.sign({
1817
+ latestKeystone: rotated,
1818
+ masterSecret: userSecret,
1819
+ claim: syncClaim,
1820
+ poolId: "secondary-native",
1821
+ metaspace: mockMetaspace,
1822
+ space: mockSpace,
1823
+ });
1824
+ }
1825
+ catch {
1826
+ oldSyncFailed = true;
1827
+ }
1828
+ iReckon(sir, oldSyncFailed).isGonnaBeTrue();
1829
+ }
1830
+ catch (error) {
1831
+ console.error(`${lc} ${extractErrorMsg(error)}`);
1832
+ throw error;
1833
+ }
1834
+ finally {
1835
+ if (logalot) {
1836
+ console.log(`${lc} complete.`);
1837
+ }
1623
1838
  }
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
1839
  });
1670
1840
  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 });
1841
+ const lc = `[fails validation if password rotation details or constraints are violated]`;
1842
+ try {
1843
+ if (logalot) {
1844
+ console.log(`${lc} starting... (I: b497f8486c58b25922d28b5efbd24826)`);
1845
+ }
1846
+ // Construct invalid change-password transition
1847
+ const userManageConfig = createStandardPoolConfig({ id: POOL_ID_MANAGE, salt: "salt1" });
1848
+ userManageConfig.allowedVerbs = [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE];
1849
+ const custodianManageConfig = createStandardPoolConfig({ id: POOL_ID_CUSTODIAN_MANAGE, salt: "salt2" });
1850
+ custodianManageConfig.allowedVerbs = [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE];
1851
+ // Custodian Pool genesis
1852
+ const strategy = KeystoneStrategyFactory.create({ config: custodianManageConfig });
1853
+ const poolSecret = await strategy.derivePoolSecret({ masterSecret: custodianSecret });
1854
+ const challenges = {};
1855
+ for (let i = 0; i < custodianManageConfig.behavior.size; i++) {
1856
+ const raw = await hash({ s: `${custodianManageConfig.salt}${Date.now()}${i}` });
1857
+ const challengeId = raw.substring(0, 16);
1858
+ const solution = await strategy.generateSolution({ poolSecret, poolId: custodianManageConfig.id, challengeId });
1859
+ challenges[challengeId] = await strategy.generateChallenge({ solution });
1860
+ }
1861
+ const custodianPool = {
1862
+ id: POOL_ID_CUSTODIAN_MANAGE,
1863
+ config: custodianManageConfig,
1864
+ challenges,
1865
+ isForeign: true
1866
+ };
1867
+ const keystone = await service.genesis({
1868
+ masterSecret: userSecret,
1869
+ configs: [userManageConfig],
1870
+ metaspace: mockMetaspace,
1871
+ space: mockSpace,
1872
+ });
1873
+ const keystoneWithCustodian = await service.addPools({
1874
+ latestKeystone: keystone,
1875
+ masterSecret: userSecret,
1876
+ newPools: [custodianPool],
1877
+ metaspace: mockMetaspace,
1878
+ space: mockSpace,
1879
+ });
1880
+ // Create an invalid frame manually where a foreign pool is rotated during change-password
1881
+ const badRotatedPool = await rotatePoolChallenges({
1882
+ prevPool: custodianPool,
1883
+ newMasterSecret: "bad_secret"
1884
+ });
1885
+ const { newKeystone: evolved } = await service.changePassword({
1886
+ keystoneIbGib: keystoneWithCustodian,
1887
+ newMasterSecret: "new_pass",
1888
+ signingSecret: userSecret,
1889
+ signingPoolId: POOL_ID_MANAGE,
1890
+ metaspace: mockMetaspace,
1891
+ space: mockSpace,
1892
+ });
1893
+ // Inject the foreign pool rotation manually to simulate an attacker
1894
+ const attackerData = {
1895
+ ...evolved.data,
1896
+ challengePools: evolved.data.challengePools.map(p => p.id === POOL_ID_CUSTODIAN_MANAGE ? badRotatedPool : p)
1897
+ };
1898
+ // Update details claim to say we rotated custodian-manage
1899
+ attackerData.proofs[0].claim.details = {
1900
+ type: KeystoneClaimType.change_password,
1901
+ info: { change: [POOL_ID_MANAGE, POOL_ID_CUSTODIAN_MANAGE] }
1902
+ };
1903
+ const attackerKeystone = {
1904
+ ...evolved,
1905
+ data: attackerData
1906
+ };
1907
+ const errors = await service.validate({
1908
+ prevIbGib: keystoneWithCustodian,
1909
+ currentIbGib: attackerKeystone
1910
+ });
1911
+ iReckon(sir, errors.length > 0).isGonnaBeTrue();
1912
+ const hasForeignViolation = errors.some(e => e.includes("Security Violation") && e.includes("foreign pool"));
1913
+ iReckon(sir, hasForeignViolation).isGonnaBeTrue();
1914
+ }
1915
+ catch (error) {
1916
+ console.error(`${lc} ${extractErrorMsg(error)}`);
1917
+ throw error;
1918
+ }
1919
+ finally {
1920
+ if (logalot) {
1921
+ console.log(`${lc} complete.`);
1922
+ }
1923
+ }
1924
+ });
1925
+ await ifWeMight(sir, 'verifies signing secrets correctly for a pool', async () => {
1926
+ const lc = `[verifies signing secrets correctly for a pool]`;
1927
+ try {
1928
+ if (logalot) {
1929
+ console.log(`${lc} starting... (I: 343eb84f5768ff0adcf2308c68404826)`);
1930
+ }
1931
+ const userSecret = "alice_secret_123";
1932
+ const builder = KeystoneProfileBuilder.buildKeystone('low')
1933
+ .withUsername('alice')
1934
+ .withEmail('alice@example.com')
1935
+ .withDescription('Alice test keystone');
1936
+ const configs = await builder.compileConfigs();
1937
+ const keystone = await service.genesis({
1938
+ masterSecret: userSecret,
1939
+ configs,
1940
+ frameDetails: builder.getFrameDetails(),
1941
+ metaspace: mockMetaspace,
1942
+ space: mockSpace,
1943
+ });
1944
+ // 1. Correct secret on manage pool
1945
+ const isCorrectManage = await service.verifySigningSecret({
1946
+ keystoneIbGib: keystone,
1947
+ signingSecret: userSecret,
1948
+ poolId: POOL_ID_MANAGE
1949
+ });
1950
+ iReckon(sir, isCorrectManage).asTo('correct manage secret').isGonnaBeTrue();
1951
+ // 2. Incorrect secret on manage pool
1952
+ const isWrongManage = await service.verifySigningSecret({
1953
+ keystoneIbGib: keystone,
1954
+ signingSecret: "wrong_secret",
1955
+ poolId: POOL_ID_MANAGE
1956
+ });
1957
+ iReckon(sir, isWrongManage).asTo('incorrect manage secret').isGonnaBeFalse();
1958
+ // 3. Correct secret on default/sign pool
1959
+ const isCorrectDefault = await service.verifySigningSecret({
1960
+ keystoneIbGib: keystone,
1961
+ signingSecret: userSecret,
1962
+ poolId: 'default'
1963
+ });
1964
+ iReckon(sir, isCorrectDefault).asTo('correct default secret').isGonnaBeTrue();
1965
+ // 4. Invalid/non-existent pool ID
1966
+ const isInvalidPool = await service.verifySigningSecret({
1967
+ keystoneIbGib: keystone,
1968
+ signingSecret: userSecret,
1969
+ poolId: 'invalid_pool_id_123'
1970
+ });
1971
+ iReckon(sir, isInvalidPool).asTo('invalid pool id').isGonnaBeFalse();
1972
+ }
1973
+ catch (error) {
1974
+ console.error(`${lc} ${extractErrorMsg(error)}`);
1975
+ throw error;
1976
+ }
1977
+ finally {
1978
+ if (logalot) {
1979
+ console.log(`${lc} complete.`);
1980
+ }
1981
+ }
1982
+ });
1983
+ await ifWeMight(sir, 'removes pools successfully, validating transitions and auth', async () => {
1984
+ const lc = `[removes pools successfully, validating transitions and auth]`;
1985
+ try {
1986
+ if (logalot) {
1987
+ console.log(`${lc} starting... (I: 2e2e747d6808b05c1e452a680d94b726)`);
1988
+ }
1989
+ const userSecret = "alice_secret_123";
1990
+ const customPoolId = "custodian-manage-google";
1991
+ const configs = [
1992
+ createStandardPoolConfig({
1993
+ id: POOL_ID_MANAGE,
1994
+ salt: "manage_salt",
1995
+ }),
1996
+ createStandardPoolConfig({
1997
+ id: customPoolId,
1998
+ salt: "custodian_salt",
1999
+ })
2000
+ ];
2001
+ const keystone = await service.genesis({
2002
+ masterSecret: userSecret,
2003
+ configs,
2004
+ metaspace: mockMetaspace,
2005
+ space: mockSpace,
2006
+ });
2007
+ // 1. Success path: remove customPoolId using correct secret
2008
+ const evolvedKeystone = await service.removePools({
2009
+ latestKeystone: keystone,
2010
+ masterSecret: userSecret,
2011
+ poolIds: [customPoolId],
2012
+ metaspace: mockMetaspace,
2013
+ space: mockSpace,
2014
+ });
2015
+ iReckon(sir, evolvedKeystone).asTo('evolved keystone exists').isGonnaBeTruthy();
2016
+ iReckon(sir, evolvedKeystone.data?.challengePools.length).asTo('pool count reduced').willEqual(1);
2017
+ iReckon(sir, evolvedKeystone.data?.challengePools[0].id).asTo('manage pool remains').willEqual(POOL_ID_MANAGE);
2018
+ // Verify transition is fully valid
2019
+ const errors = await service.validate({
2020
+ prevIbGib: keystone,
2021
+ currentIbGib: evolvedKeystone
2022
+ });
2023
+ iReckon(sir, errors.length).asTo('transition validation succeeds').willEqual(0);
2024
+ // 2. Sad path: invalid master secret
2025
+ let cryptoErrorCaught = false;
2026
+ try {
2027
+ await service.removePools({
2028
+ latestKeystone: keystone,
2029
+ masterSecret: "wrong_secret",
2030
+ poolIds: [customPoolId],
2031
+ metaspace: mockMetaspace,
2032
+ space: mockSpace,
2033
+ });
2034
+ }
2035
+ catch (e) {
2036
+ if (e.message.includes('Crypto Violation')) {
2037
+ cryptoErrorCaught = true;
2038
+ }
2039
+ }
2040
+ iReckon(sir, cryptoErrorCaught).asTo('forgery check prevents removal').isGonnaBeTrue();
2041
+ // 3. Sad path: non-existent pool ID
2042
+ let notFoundErrorCaught = false;
2043
+ try {
2044
+ await service.removePools({
2045
+ latestKeystone: keystone,
2046
+ masterSecret: userSecret,
2047
+ poolIds: ["non-existent-pool"],
2048
+ metaspace: mockMetaspace,
2049
+ space: mockSpace,
2050
+ });
2051
+ }
2052
+ catch (e) {
2053
+ if (e.message.includes('not found in keystone')) {
2054
+ notFoundErrorCaught = true;
2055
+ }
2056
+ }
2057
+ iReckon(sir, notFoundErrorCaught).asTo('non-existent pool ID rejected').isGonnaBeTrue();
2058
+ }
2059
+ catch (error) {
2060
+ console.error(`${lc} ${extractErrorMsg(error)}`);
2061
+ throw error;
2062
+ }
2063
+ finally {
2064
+ if (logalot) {
2065
+ console.log(`${lc} complete.`);
2066
+ }
2067
+ }
2068
+ });
2069
+ await ifWeMight(sir, 'rotates registered delegate keystone challenge pools when changing user password', async () => {
2070
+ const lc = `[rotates registered delegate keystone challenge pools when changing user password]`;
2071
+ try {
2072
+ if (logalot) {
2073
+ console.log(`${lc} starting...`);
2074
+ }
2075
+ const userSecret = "alice_initial_pass_123";
2076
+ const newSecret = "alice_new_pass_456";
2077
+ // 1. Create primary keystone
2078
+ const parentConfigs = [
2079
+ createStandardPoolConfig({ id: POOL_ID_MANAGE, salt: "parent_manage_salt" }),
2080
+ createStandardPoolConfig({ id: POOL_ID_DEFAULT, salt: "parent_default_salt" })
2081
+ ];
2082
+ const parentKeystone = await service.genesis({
2083
+ masterSecret: userSecret,
2084
+ configs: parentConfigs,
2085
+ metaspace: mockMetaspace,
2086
+ space: mockSpace,
2087
+ });
2088
+ // 2. Create delegate keystone using derived delegateSecret from userSecret (matching real app creation behavior)
2089
+ const delegateSecret = await deriveDelegateSecret({ masterSecret: userSecret });
2090
+ const delegateConfigs = [
2091
+ createStandardPoolConfig({ id: POOL_ID_MANAGE, salt: "delegate_manage_salt" }),
2092
+ createStandardPoolConfig({ id: "delegate_sync_pool", salt: "delegate_sync_salt" })
2093
+ ];
2094
+ const delegateKeystone = await service.genesis({
2095
+ masterSecret: delegateSecret,
2096
+ configs: delegateConfigs,
2097
+ metaspace: mockMetaspace,
2098
+ space: mockSpace,
2099
+ });
2100
+ const delegateTjp = getTjpAddr({ ibGib: delegateKeystone });
2101
+ if (!delegateTjp) {
2102
+ throw new Error('delegateTjp missing');
2103
+ }
2104
+ // 3. Register delegate under primary keystone
2105
+ const registeredParent = await service.registerDelegate({
2106
+ parentKeystone,
2107
+ delegateKeystone,
2108
+ masterSecret: userSecret,
2109
+ metaspace: mockMetaspace,
2110
+ space: mockSpace,
2111
+ allowedVerbs: [KEYSTONE_VERB_SYNC],
2112
+ });
2113
+ // 4. Change user password on parent keystone.
2114
+ // Note: We explicitly set `poolIds: undefined` to reinforce that passing undefined
2115
+ // instructs changePassword to rotate all non-foreign pools on the primary keystone
2116
+ // AND all registered outward delegates. (In production caller code, poolIds parameter can simply be omitted).
2117
+ const { newKeystone: evolvedParent, newDelegates } = await service.changePassword({
2118
+ keystoneIbGib: registeredParent,
2119
+ newMasterSecret: newSecret,
2120
+ signingSecret: userSecret,
2121
+ signingPoolId: POOL_ID_MANAGE,
2122
+ poolIds: undefined,
2123
+ metaspace: mockMetaspace,
2124
+ space: mockSpace,
2125
+ });
2126
+ // 5. Retrieve latest delegate keystone from metaspace/space
2127
+ if (newDelegates && newDelegates.length === 1) {
2128
+ const latestDelegate = newDelegates[0];
2129
+ // 6. Verify primary keystone challenge pools have rotated to new secret
2130
+ for (const pool of evolvedParent.data.challengePools) {
2131
+ if (!pool.isForeign) {
2132
+ const oldValid = await service.verifySigningSecret({
2133
+ keystoneIbGib: evolvedParent,
2134
+ signingSecret: userSecret,
2135
+ poolId: pool.id
2136
+ });
2137
+ iReckon(sir, oldValid).asTo(`parent pool ${pool.id} old secret invalid`).isGonnaBeFalse();
2138
+ const newValid = await service.verifySigningSecret({
2139
+ keystoneIbGib: evolvedParent,
2140
+ signingSecret: newSecret,
2141
+ poolId: pool.id
2142
+ });
2143
+ iReckon(sir, newValid).asTo(`parent pool ${pool.id} new secret valid`).isGonnaBeTrue();
2144
+ }
2145
+ }
2146
+ // 7. Verify delegate keystone challenge pools have rotated to new secret
2147
+ const oldDelegateSecret = await deriveDelegateSecret({ masterSecret: userSecret });
2148
+ const newDelegateSecret = await deriveDelegateSecret({ masterSecret: newSecret });
2149
+ for (const pool of latestDelegate.data.challengePools) {
2150
+ if (!pool.isForeign) {
2151
+ const oldValid = await service.verifySigningSecret({
2152
+ keystoneIbGib: latestDelegate,
2153
+ signingSecret: oldDelegateSecret,
2154
+ poolId: pool.id
2155
+ });
2156
+ iReckon(sir, oldValid).asTo(`delegate pool ${pool.id} old secret invalid`).isGonnaBeFalse();
2157
+ const newValid = await service.verifySigningSecret({
2158
+ keystoneIbGib: latestDelegate,
2159
+ signingSecret: newDelegateSecret,
2160
+ poolId: pool.id
2161
+ });
2162
+ iReckon(sir, newValid).asTo(`delegate pool ${pool.id} new secret valid`).isGonnaBeTrue();
2163
+ }
2164
+ }
2165
+ }
2166
+ else {
2167
+ iReckon(sir, true).asTo('newDelegates should have had length 1 but is empty/falsy').isGonnaBeFalse();
2168
+ }
2169
+ }
2170
+ catch (error) {
2171
+ console.error(`${lc} ${extractErrorMsg(error)}`);
2172
+ throw error;
2173
+ }
2174
+ finally {
2175
+ if (logalot) {
2176
+ console.log(`${lc} complete.`);
2177
+ }
1685
2178
  }
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
2179
  });
1740
2180
  });
1741
2181
  //# sourceMappingURL=keystone-service-v1.respec.mjs.map