@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.
- package/dist/keystone/keystone-constants.d.mts +1 -0
- package/dist/keystone/keystone-constants.d.mts.map +1 -1
- package/dist/keystone/keystone-constants.mjs +1 -0
- package/dist/keystone/keystone-constants.mjs.map +1 -1
- package/dist/keystone/keystone-helpers.d.mts +19 -1
- package/dist/keystone/keystone-helpers.d.mts.map +1 -1
- package/dist/keystone/keystone-helpers.mjs +191 -16
- package/dist/keystone/keystone-helpers.mjs.map +1 -1
- package/dist/keystone/keystone-service-v1.d.mts +82 -1
- package/dist/keystone/keystone-service-v1.d.mts.map +1 -1
- package/dist/keystone/keystone-service-v1.mjs +321 -27
- package/dist/keystone/keystone-service-v1.mjs.map +1 -1
- package/dist/keystone/keystone-service-v1.respec.mjs +474 -47
- package/dist/keystone/keystone-service-v1.respec.mjs.map +1 -1
- package/dist/keystone/keystone-types.d.mts +26 -2
- package/dist/keystone/keystone-types.d.mts.map +1 -1
- package/dist/keystone/keystone-types.mjs +13 -0
- package/dist/keystone/keystone-types.mjs.map +1 -1
- package/dist/keystone/policy/profiles/profile-domain.json +0 -15
- package/dist/keystone/policy/schemas/keystone.high.schema.json +1 -1
- package/dist/keystone/policy/schemas/keystone.medium.schema.json +1 -1
- package/package.json +2 -2
- package/src/keystone/keystone-constants.mts +1 -0
- package/src/keystone/keystone-helpers.mts +223 -15
- package/src/keystone/keystone-service-v1.mts +387 -28
- package/src/keystone/keystone-service-v1.respec.mts +1990 -1500
- package/src/keystone/keystone-types.mts +43 -2
- package/src/keystone/policy/profiles/profile-domain.json +1 -16
- package/src/keystone/policy/schemas/keystone.high.schema.json +1 -1
- 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
|
-
|
|
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
|
-
|
|
405
|
-
|
|
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
|
}
|