dignity.js 0.6.0 → 0.7.1
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/README.md +97 -1
- package/dist/dignity.cjs.js +802 -16
- package/dist/dignity.cjs.js.map +4 -4
- package/dist/dignity.esm.js +795 -9
- package/dist/dignity.esm.js.map +3 -3
- package/dist/dignity.min.js +9 -9
- package/docs/assets/dignity.esm.js +928 -30
- package/docs/assets/playground-demos.js +342 -0
- package/docs/assets/playground.css +277 -0
- package/docs/assets/playground.js +248 -0
- package/docs/assets/styles.css +18 -2
- package/docs/index.html +23 -3
- package/docs/openapi-like.json +1 -1
- package/package.json +5 -3
- package/src/core/dignity-p2p.js +229 -4
- package/src/index.js +25 -1
- package/src/security/derive-key-pair.js +147 -0
- package/src/security/identity-rotation.js +427 -0
- package/src/security/message-security-service.js +94 -4
package/dist/dignity.esm.js
CHANGED
|
@@ -2444,19 +2444,511 @@ var require_vdf = __commonJS({
|
|
|
2444
2444
|
}
|
|
2445
2445
|
});
|
|
2446
2446
|
|
|
2447
|
+
// src/security/derive-key-pair.js
|
|
2448
|
+
var require_derive_key_pair = __commonJS({
|
|
2449
|
+
"src/security/derive-key-pair.js"(exports, module) {
|
|
2450
|
+
var nacl = require_nacl_fast();
|
|
2451
|
+
var naclUtil = require_nacl_util();
|
|
2452
|
+
var { deriveBroadcastKey, DEFAULT_SECURITY_OPTIONS } = require_message_security_service();
|
|
2453
|
+
var SIGNING_INFO = "dignity-signing-v1";
|
|
2454
|
+
var ENCRYPTION_INFO = "dignity-encryption-v1";
|
|
2455
|
+
var COLD_RECOVERY_INFO = "dignity-cold-recovery-v1";
|
|
2456
|
+
function utf8ToBytes(value) {
|
|
2457
|
+
return naclUtil.decodeUTF8(value);
|
|
2458
|
+
}
|
|
2459
|
+
function concatBytes(...parts) {
|
|
2460
|
+
const total = parts.reduce((sum, part) => sum + part.length, 0);
|
|
2461
|
+
const result = new Uint8Array(total);
|
|
2462
|
+
let offset = 0;
|
|
2463
|
+
for (const part of parts) {
|
|
2464
|
+
result.set(part, offset);
|
|
2465
|
+
offset += part.length;
|
|
2466
|
+
}
|
|
2467
|
+
return result;
|
|
2468
|
+
}
|
|
2469
|
+
function buildColdRecoverySalt(username, pepper = "") {
|
|
2470
|
+
if (!username || typeof username !== "string") {
|
|
2471
|
+
throw new Error("deriveColdRecoverySigningKey requires username");
|
|
2472
|
+
}
|
|
2473
|
+
const segments = ["dignity-cold-recovery-v1"];
|
|
2474
|
+
if (pepper) {
|
|
2475
|
+
segments.push(pepper);
|
|
2476
|
+
}
|
|
2477
|
+
segments.push(username, COLD_RECOVERY_INFO);
|
|
2478
|
+
return utf8ToBytes(segments.join("\0"));
|
|
2479
|
+
}
|
|
2480
|
+
async function deriveColdRecoverySigningKey({
|
|
2481
|
+
username,
|
|
2482
|
+
coldPassword,
|
|
2483
|
+
pepper = "",
|
|
2484
|
+
kdfIterations
|
|
2485
|
+
} = {}) {
|
|
2486
|
+
if (!coldPassword || typeof coldPassword !== "string") {
|
|
2487
|
+
throw new Error("deriveColdRecoverySigningKey requires coldPassword");
|
|
2488
|
+
}
|
|
2489
|
+
const salt = buildColdRecoverySalt(username, pepper);
|
|
2490
|
+
const iterations = typeof kdfIterations === "number" ? kdfIterations : DEFAULT_SECURITY_OPTIONS.kdfIterations;
|
|
2491
|
+
const seed = await deriveBroadcastKey(coldPassword, salt, iterations);
|
|
2492
|
+
const signing = nacl.sign.keyPair.fromSeed(seed);
|
|
2493
|
+
return {
|
|
2494
|
+
signing,
|
|
2495
|
+
recoveryPublicKey: naclUtil.encodeBase64(signing.publicKey)
|
|
2496
|
+
};
|
|
2497
|
+
}
|
|
2498
|
+
function buildIdentitySalt(username, info, pepper = "", generation = 1) {
|
|
2499
|
+
if (!username || typeof username !== "string") {
|
|
2500
|
+
throw new Error("deriveKeyPairFromCredentials requires username");
|
|
2501
|
+
}
|
|
2502
|
+
if (!info || typeof info !== "string") {
|
|
2503
|
+
throw new Error("deriveKeyPairFromCredentials requires info label");
|
|
2504
|
+
}
|
|
2505
|
+
const normalizedGeneration = Number(generation);
|
|
2506
|
+
if (!Number.isInteger(normalizedGeneration) || normalizedGeneration < 1) {
|
|
2507
|
+
throw new Error("deriveKeyPairFromCredentials requires generation >= 1");
|
|
2508
|
+
}
|
|
2509
|
+
const segments = ["dignity-identity-v1"];
|
|
2510
|
+
if (pepper) {
|
|
2511
|
+
segments.push(pepper);
|
|
2512
|
+
}
|
|
2513
|
+
segments.push(username, `gen:${normalizedGeneration}`, info);
|
|
2514
|
+
return utf8ToBytes(segments.join("\0"));
|
|
2515
|
+
}
|
|
2516
|
+
async function deriveIdentitySeed({ password, username, info, pepper, generation, kdfIterations }) {
|
|
2517
|
+
if (!password || typeof password !== "string") {
|
|
2518
|
+
throw new Error("deriveKeyPairFromCredentials requires password");
|
|
2519
|
+
}
|
|
2520
|
+
const salt = buildIdentitySalt(username, info, pepper, generation);
|
|
2521
|
+
const iterations = typeof kdfIterations === "number" ? kdfIterations : DEFAULT_SECURITY_OPTIONS.kdfIterations;
|
|
2522
|
+
return deriveBroadcastKey(password, salt, iterations);
|
|
2523
|
+
}
|
|
2524
|
+
async function deriveKeyPairFromCredentials({
|
|
2525
|
+
username,
|
|
2526
|
+
password,
|
|
2527
|
+
pepper = "",
|
|
2528
|
+
generation = 1,
|
|
2529
|
+
kdfIterations
|
|
2530
|
+
} = {}) {
|
|
2531
|
+
const signingSeed = await deriveIdentitySeed({
|
|
2532
|
+
password,
|
|
2533
|
+
username,
|
|
2534
|
+
info: SIGNING_INFO,
|
|
2535
|
+
pepper,
|
|
2536
|
+
generation,
|
|
2537
|
+
kdfIterations
|
|
2538
|
+
});
|
|
2539
|
+
const encryptionSecret = await deriveIdentitySeed({
|
|
2540
|
+
password,
|
|
2541
|
+
username,
|
|
2542
|
+
info: ENCRYPTION_INFO,
|
|
2543
|
+
pepper,
|
|
2544
|
+
generation,
|
|
2545
|
+
kdfIterations
|
|
2546
|
+
});
|
|
2547
|
+
return {
|
|
2548
|
+
signing: nacl.sign.keyPair.fromSeed(signingSeed),
|
|
2549
|
+
encryption: nacl.box.keyPair.fromSecretKey(encryptionSecret),
|
|
2550
|
+
generation
|
|
2551
|
+
};
|
|
2552
|
+
}
|
|
2553
|
+
function keyPairToPublicBundle(keyPair) {
|
|
2554
|
+
return {
|
|
2555
|
+
signingPublicKey: naclUtil.encodeBase64(keyPair.signing.publicKey),
|
|
2556
|
+
encryptionPublicKey: naclUtil.encodeBase64(keyPair.encryption.publicKey)
|
|
2557
|
+
};
|
|
2558
|
+
}
|
|
2559
|
+
module.exports = {
|
|
2560
|
+
deriveKeyPairFromCredentials,
|
|
2561
|
+
deriveColdRecoverySigningKey,
|
|
2562
|
+
keyPairToPublicBundle,
|
|
2563
|
+
buildIdentitySalt,
|
|
2564
|
+
buildColdRecoverySalt,
|
|
2565
|
+
SIGNING_INFO,
|
|
2566
|
+
ENCRYPTION_INFO,
|
|
2567
|
+
COLD_RECOVERY_INFO,
|
|
2568
|
+
concatBytes
|
|
2569
|
+
};
|
|
2570
|
+
}
|
|
2571
|
+
});
|
|
2572
|
+
|
|
2573
|
+
// src/security/identity-rotation.js
|
|
2574
|
+
var require_identity_rotation = __commonJS({
|
|
2575
|
+
"src/security/identity-rotation.js"(exports, module) {
|
|
2576
|
+
var nacl = require_nacl_fast();
|
|
2577
|
+
var naclUtil = require_nacl_util();
|
|
2578
|
+
var { stableStringify } = require_message_security_service();
|
|
2579
|
+
var {
|
|
2580
|
+
deriveKeyPairFromCredentials,
|
|
2581
|
+
deriveColdRecoverySigningKey,
|
|
2582
|
+
keyPairToPublicBundle
|
|
2583
|
+
} = require_derive_key_pair();
|
|
2584
|
+
var ROTATION_TYPES = /* @__PURE__ */ new Set(["compromise-recovery", "password-change"]);
|
|
2585
|
+
function utf8ToBytes(value) {
|
|
2586
|
+
return naclUtil.decodeUTF8(value);
|
|
2587
|
+
}
|
|
2588
|
+
function normalizePublicKeyBundle(publicKey) {
|
|
2589
|
+
if (!publicKey || !publicKey.signingPublicKey || !publicKey.encryptionPublicKey) {
|
|
2590
|
+
throw new Error("Public key bundle requires signingPublicKey and encryptionPublicKey");
|
|
2591
|
+
}
|
|
2592
|
+
return {
|
|
2593
|
+
signingPublicKey: publicKey.signingPublicKey,
|
|
2594
|
+
encryptionPublicKey: publicKey.encryptionPublicKey
|
|
2595
|
+
};
|
|
2596
|
+
}
|
|
2597
|
+
function buildIdentityRotationPayload({
|
|
2598
|
+
username,
|
|
2599
|
+
fromGeneration,
|
|
2600
|
+
toGeneration,
|
|
2601
|
+
previousPublicKey,
|
|
2602
|
+
nextPublicKey,
|
|
2603
|
+
rotationKind,
|
|
2604
|
+
reason,
|
|
2605
|
+
timestamp
|
|
2606
|
+
}) {
|
|
2607
|
+
if (!username) {
|
|
2608
|
+
throw new Error("Identity rotation requires username");
|
|
2609
|
+
}
|
|
2610
|
+
if (!ROTATION_TYPES.has(rotationKind)) {
|
|
2611
|
+
throw new Error(`Identity rotation kind must be one of: ${[...ROTATION_TYPES].join(", ")}`);
|
|
2612
|
+
}
|
|
2613
|
+
if (toGeneration !== fromGeneration + 1) {
|
|
2614
|
+
throw new Error("Identity rotation must advance generation by exactly 1");
|
|
2615
|
+
}
|
|
2616
|
+
return {
|
|
2617
|
+
version: 1,
|
|
2618
|
+
type: "identity:rotate",
|
|
2619
|
+
username,
|
|
2620
|
+
fromGeneration,
|
|
2621
|
+
toGeneration,
|
|
2622
|
+
previousPublicKey: normalizePublicKeyBundle(previousPublicKey),
|
|
2623
|
+
nextPublicKey: normalizePublicKeyBundle(nextPublicKey),
|
|
2624
|
+
rotationKind,
|
|
2625
|
+
reason: reason || "",
|
|
2626
|
+
timestamp: typeof timestamp === "number" ? timestamp : Date.now()
|
|
2627
|
+
};
|
|
2628
|
+
}
|
|
2629
|
+
function signIdentityRotationPayload(payload, signingSecretKey) {
|
|
2630
|
+
const message = utf8ToBytes(stableStringify(payload));
|
|
2631
|
+
const signature = nacl.sign.detached(message, signingSecretKey);
|
|
2632
|
+
return naclUtil.encodeBase64(signature);
|
|
2633
|
+
}
|
|
2634
|
+
function verifyDetachedSignature(payload, signatureBase64, signingPublicKeyBase64) {
|
|
2635
|
+
const message = utf8ToBytes(stableStringify(payload));
|
|
2636
|
+
const signatureBytes = naclUtil.decodeBase64(signatureBase64);
|
|
2637
|
+
const signingPublicKey = naclUtil.decodeBase64(signingPublicKeyBase64);
|
|
2638
|
+
return nacl.sign.detached.verify(message, signatureBytes, signingPublicKey);
|
|
2639
|
+
}
|
|
2640
|
+
function createIdentityRotation({
|
|
2641
|
+
username,
|
|
2642
|
+
fromGeneration,
|
|
2643
|
+
toGeneration,
|
|
2644
|
+
previousPublicKey,
|
|
2645
|
+
nextKeyPair,
|
|
2646
|
+
rotationKind,
|
|
2647
|
+
reason,
|
|
2648
|
+
timestamp,
|
|
2649
|
+
coldRecoverySigningSecretKey
|
|
2650
|
+
}) {
|
|
2651
|
+
if (!nextKeyPair || !nextKeyPair.signing || !nextKeyPair.signing.secretKey) {
|
|
2652
|
+
throw new Error("Identity rotation requires nextKeyPair with signing secret");
|
|
2653
|
+
}
|
|
2654
|
+
const payload = buildIdentityRotationPayload({
|
|
2655
|
+
username,
|
|
2656
|
+
fromGeneration,
|
|
2657
|
+
toGeneration,
|
|
2658
|
+
previousPublicKey,
|
|
2659
|
+
nextPublicKey: keyPairToPublicBundle(nextKeyPair),
|
|
2660
|
+
rotationKind,
|
|
2661
|
+
reason,
|
|
2662
|
+
timestamp
|
|
2663
|
+
});
|
|
2664
|
+
const rotation = {
|
|
2665
|
+
...payload,
|
|
2666
|
+
signature: signIdentityRotationPayload(payload, nextKeyPair.signing.secretKey)
|
|
2667
|
+
};
|
|
2668
|
+
if (coldRecoverySigningSecretKey) {
|
|
2669
|
+
rotation.recoverySignature = signIdentityRotationPayload(
|
|
2670
|
+
payload,
|
|
2671
|
+
coldRecoverySigningSecretKey
|
|
2672
|
+
);
|
|
2673
|
+
}
|
|
2674
|
+
return rotation;
|
|
2675
|
+
}
|
|
2676
|
+
function buildColdRecoveryEnrollmentPayload({ username, recoveryPublicKey, timestamp }) {
|
|
2677
|
+
if (!username) {
|
|
2678
|
+
throw new Error("Cold recovery enrollment requires username");
|
|
2679
|
+
}
|
|
2680
|
+
if (!recoveryPublicKey) {
|
|
2681
|
+
throw new Error("Cold recovery enrollment requires recoveryPublicKey");
|
|
2682
|
+
}
|
|
2683
|
+
return {
|
|
2684
|
+
version: 1,
|
|
2685
|
+
type: "identity:cold-enroll",
|
|
2686
|
+
username,
|
|
2687
|
+
recoveryPublicKey,
|
|
2688
|
+
timestamp: typeof timestamp === "number" ? timestamp : Date.now()
|
|
2689
|
+
};
|
|
2690
|
+
}
|
|
2691
|
+
function createColdRecoveryEnrollment({
|
|
2692
|
+
username,
|
|
2693
|
+
coldRecoverySigningSecretKey,
|
|
2694
|
+
recoveryPublicKey,
|
|
2695
|
+
timestamp
|
|
2696
|
+
}) {
|
|
2697
|
+
if (!coldRecoverySigningSecretKey) {
|
|
2698
|
+
throw new Error("Cold recovery enrollment requires cold recovery signing secret");
|
|
2699
|
+
}
|
|
2700
|
+
if (!recoveryPublicKey) {
|
|
2701
|
+
throw new Error("Cold recovery enrollment requires recoveryPublicKey");
|
|
2702
|
+
}
|
|
2703
|
+
const payload = buildColdRecoveryEnrollmentPayload({
|
|
2704
|
+
username,
|
|
2705
|
+
recoveryPublicKey,
|
|
2706
|
+
timestamp
|
|
2707
|
+
});
|
|
2708
|
+
return {
|
|
2709
|
+
...payload,
|
|
2710
|
+
signature: signIdentityRotationPayload(payload, coldRecoverySigningSecretKey)
|
|
2711
|
+
};
|
|
2712
|
+
}
|
|
2713
|
+
function verifyColdRecoveryEnrollment(enrollment) {
|
|
2714
|
+
if (!enrollment || enrollment.type !== "identity:cold-enroll" || enrollment.version !== 1) {
|
|
2715
|
+
return { ok: false, error: "invalid-enrollment-shape" };
|
|
2716
|
+
}
|
|
2717
|
+
if (!enrollment.signature || !enrollment.recoveryPublicKey) {
|
|
2718
|
+
return { ok: false, error: "missing-enrollment-fields" };
|
|
2719
|
+
}
|
|
2720
|
+
const { signature, ...payload } = enrollment;
|
|
2721
|
+
const verified = verifyDetachedSignature(payload, signature, enrollment.recoveryPublicKey);
|
|
2722
|
+
if (!verified) {
|
|
2723
|
+
return { ok: false, error: "invalid-enrollment-signature" };
|
|
2724
|
+
}
|
|
2725
|
+
return { ok: true, enrollment };
|
|
2726
|
+
}
|
|
2727
|
+
function verifyIdentityRotation(rotation, options = {}) {
|
|
2728
|
+
if (!rotation || rotation.type !== "identity:rotate" || rotation.version !== 1) {
|
|
2729
|
+
return { ok: false, error: "invalid-rotation-shape" };
|
|
2730
|
+
}
|
|
2731
|
+
if (!rotation.signature || !rotation.nextPublicKey || !rotation.previousPublicKey) {
|
|
2732
|
+
return { ok: false, error: "missing-rotation-fields" };
|
|
2733
|
+
}
|
|
2734
|
+
if (rotation.toGeneration !== rotation.fromGeneration + 1) {
|
|
2735
|
+
return { ok: false, error: "invalid-generation-step" };
|
|
2736
|
+
}
|
|
2737
|
+
if (!ROTATION_TYPES.has(rotation.rotationKind)) {
|
|
2738
|
+
return { ok: false, error: "invalid-rotation-kind" };
|
|
2739
|
+
}
|
|
2740
|
+
const { signature, recoverySignature, ...payload } = rotation;
|
|
2741
|
+
const verified = verifyDetachedSignature(payload, signature, rotation.nextPublicKey.signingPublicKey);
|
|
2742
|
+
if (!verified) {
|
|
2743
|
+
return { ok: false, error: "invalid-signature" };
|
|
2744
|
+
}
|
|
2745
|
+
const requiredRecoveryPublicKey = options.requiredRecoveryPublicKey || null;
|
|
2746
|
+
if (requiredRecoveryPublicKey) {
|
|
2747
|
+
if (!recoverySignature) {
|
|
2748
|
+
return { ok: false, error: "missing-recovery-signature" };
|
|
2749
|
+
}
|
|
2750
|
+
const recoveryVerified = verifyDetachedSignature(
|
|
2751
|
+
payload,
|
|
2752
|
+
recoverySignature,
|
|
2753
|
+
requiredRecoveryPublicKey
|
|
2754
|
+
);
|
|
2755
|
+
if (!recoveryVerified) {
|
|
2756
|
+
return { ok: false, error: "invalid-recovery-signature" };
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
if (rotation.previousPublicKey.signingPublicKey === rotation.nextPublicKey.signingPublicKey) {
|
|
2760
|
+
return { ok: false, error: "unchanged-signing-key" };
|
|
2761
|
+
}
|
|
2762
|
+
return { ok: true, rotation };
|
|
2763
|
+
}
|
|
2764
|
+
async function resolveColdRecoverySigningSecretKey({
|
|
2765
|
+
username,
|
|
2766
|
+
coldPassword,
|
|
2767
|
+
pepper,
|
|
2768
|
+
kdfIterations
|
|
2769
|
+
}) {
|
|
2770
|
+
if (!coldPassword) {
|
|
2771
|
+
return null;
|
|
2772
|
+
}
|
|
2773
|
+
const coldRecovery = await deriveColdRecoverySigningKey({
|
|
2774
|
+
username,
|
|
2775
|
+
coldPassword,
|
|
2776
|
+
pepper,
|
|
2777
|
+
kdfIterations
|
|
2778
|
+
});
|
|
2779
|
+
return coldRecovery.signing.secretKey;
|
|
2780
|
+
}
|
|
2781
|
+
async function revokeAndRotateIdentity({
|
|
2782
|
+
username,
|
|
2783
|
+
password,
|
|
2784
|
+
coldPassword,
|
|
2785
|
+
currentGeneration = 1,
|
|
2786
|
+
reason = "compromise-recovery",
|
|
2787
|
+
pepper = "",
|
|
2788
|
+
kdfIterations,
|
|
2789
|
+
timestamp
|
|
2790
|
+
} = {}) {
|
|
2791
|
+
const currentKeyPair = await deriveKeyPairFromCredentials({
|
|
2792
|
+
username,
|
|
2793
|
+
password,
|
|
2794
|
+
generation: currentGeneration,
|
|
2795
|
+
pepper,
|
|
2796
|
+
kdfIterations
|
|
2797
|
+
});
|
|
2798
|
+
const nextGeneration = currentGeneration + 1;
|
|
2799
|
+
const nextKeyPair = await deriveKeyPairFromCredentials({
|
|
2800
|
+
username,
|
|
2801
|
+
password,
|
|
2802
|
+
generation: nextGeneration,
|
|
2803
|
+
pepper,
|
|
2804
|
+
kdfIterations
|
|
2805
|
+
});
|
|
2806
|
+
const coldRecoverySigningSecretKey = await resolveColdRecoverySigningSecretKey({
|
|
2807
|
+
username,
|
|
2808
|
+
coldPassword,
|
|
2809
|
+
pepper,
|
|
2810
|
+
kdfIterations
|
|
2811
|
+
});
|
|
2812
|
+
const rotation = createIdentityRotation({
|
|
2813
|
+
username,
|
|
2814
|
+
fromGeneration: currentGeneration,
|
|
2815
|
+
toGeneration: nextGeneration,
|
|
2816
|
+
previousPublicKey: keyPairToPublicBundle(currentKeyPair),
|
|
2817
|
+
nextKeyPair,
|
|
2818
|
+
rotationKind: "compromise-recovery",
|
|
2819
|
+
reason,
|
|
2820
|
+
timestamp,
|
|
2821
|
+
coldRecoverySigningSecretKey
|
|
2822
|
+
});
|
|
2823
|
+
return {
|
|
2824
|
+
rotation,
|
|
2825
|
+
currentKeyPair,
|
|
2826
|
+
nextKeyPair,
|
|
2827
|
+
nextGeneration,
|
|
2828
|
+
coldRecoveryUsed: Boolean(coldRecoverySigningSecretKey)
|
|
2829
|
+
};
|
|
2830
|
+
}
|
|
2831
|
+
async function rotateIdentityPassword({
|
|
2832
|
+
username,
|
|
2833
|
+
currentPassword,
|
|
2834
|
+
newPassword,
|
|
2835
|
+
coldPassword,
|
|
2836
|
+
currentGeneration = 1,
|
|
2837
|
+
reason = "password-change",
|
|
2838
|
+
pepper = "",
|
|
2839
|
+
kdfIterations,
|
|
2840
|
+
timestamp
|
|
2841
|
+
} = {}) {
|
|
2842
|
+
const currentKeyPair = await deriveKeyPairFromCredentials({
|
|
2843
|
+
username,
|
|
2844
|
+
password: currentPassword,
|
|
2845
|
+
generation: currentGeneration,
|
|
2846
|
+
pepper,
|
|
2847
|
+
kdfIterations
|
|
2848
|
+
});
|
|
2849
|
+
const nextGeneration = currentGeneration + 1;
|
|
2850
|
+
const nextKeyPair = await deriveKeyPairFromCredentials({
|
|
2851
|
+
username,
|
|
2852
|
+
password: newPassword,
|
|
2853
|
+
generation: nextGeneration,
|
|
2854
|
+
pepper,
|
|
2855
|
+
kdfIterations
|
|
2856
|
+
});
|
|
2857
|
+
const coldRecoverySigningSecretKey = await resolveColdRecoverySigningSecretKey({
|
|
2858
|
+
username,
|
|
2859
|
+
coldPassword,
|
|
2860
|
+
pepper,
|
|
2861
|
+
kdfIterations
|
|
2862
|
+
});
|
|
2863
|
+
const rotation = createIdentityRotation({
|
|
2864
|
+
username,
|
|
2865
|
+
fromGeneration: currentGeneration,
|
|
2866
|
+
toGeneration: nextGeneration,
|
|
2867
|
+
previousPublicKey: keyPairToPublicBundle(currentKeyPair),
|
|
2868
|
+
nextKeyPair,
|
|
2869
|
+
rotationKind: "password-change",
|
|
2870
|
+
reason,
|
|
2871
|
+
timestamp,
|
|
2872
|
+
coldRecoverySigningSecretKey
|
|
2873
|
+
});
|
|
2874
|
+
return {
|
|
2875
|
+
rotation,
|
|
2876
|
+
currentKeyPair,
|
|
2877
|
+
nextKeyPair,
|
|
2878
|
+
nextGeneration,
|
|
2879
|
+
coldRecoveryUsed: Boolean(coldRecoverySigningSecretKey)
|
|
2880
|
+
};
|
|
2881
|
+
}
|
|
2882
|
+
async function enrollColdRecoveryPassword({
|
|
2883
|
+
username,
|
|
2884
|
+
coldPassword,
|
|
2885
|
+
pepper = "",
|
|
2886
|
+
kdfIterations,
|
|
2887
|
+
timestamp
|
|
2888
|
+
} = {}) {
|
|
2889
|
+
const coldRecovery = await deriveColdRecoverySigningKey({
|
|
2890
|
+
username,
|
|
2891
|
+
coldPassword,
|
|
2892
|
+
pepper,
|
|
2893
|
+
kdfIterations
|
|
2894
|
+
});
|
|
2895
|
+
const enrollment = createColdRecoveryEnrollment({
|
|
2896
|
+
username,
|
|
2897
|
+
coldRecoverySigningSecretKey: coldRecovery.signing.secretKey,
|
|
2898
|
+
recoveryPublicKey: coldRecovery.recoveryPublicKey,
|
|
2899
|
+
timestamp
|
|
2900
|
+
});
|
|
2901
|
+
return {
|
|
2902
|
+
enrollment,
|
|
2903
|
+
recoveryPublicKey: coldRecovery.recoveryPublicKey,
|
|
2904
|
+
coldRecovery
|
|
2905
|
+
};
|
|
2906
|
+
}
|
|
2907
|
+
function shouldApplyIdentityRotation(currentState, rotation, options = {}) {
|
|
2908
|
+
const requiredRecoveryPublicKey = options.enrolledRecoveryPublicKey || currentState && currentState.recoveryPublicKey || null;
|
|
2909
|
+
const verified = verifyIdentityRotation(rotation, { requiredRecoveryPublicKey });
|
|
2910
|
+
if (!verified.ok) {
|
|
2911
|
+
return { apply: false, reason: verified.error };
|
|
2912
|
+
}
|
|
2913
|
+
if (currentState && rotation.toGeneration <= currentState.generation) {
|
|
2914
|
+
return { apply: false, reason: "stale-generation" };
|
|
2915
|
+
}
|
|
2916
|
+
if (currentState && currentState.publicKey && currentState.publicKey.signingPublicKey !== rotation.previousPublicKey.signingPublicKey) {
|
|
2917
|
+
return { apply: false, reason: "previous-key-mismatch" };
|
|
2918
|
+
}
|
|
2919
|
+
return { apply: true, rotation: verified.rotation };
|
|
2920
|
+
}
|
|
2921
|
+
module.exports = {
|
|
2922
|
+
createIdentityRotation,
|
|
2923
|
+
createColdRecoveryEnrollment,
|
|
2924
|
+
verifyIdentityRotation,
|
|
2925
|
+
verifyColdRecoveryEnrollment,
|
|
2926
|
+
revokeAndRotateIdentity,
|
|
2927
|
+
rotateIdentityPassword,
|
|
2928
|
+
enrollColdRecoveryPassword,
|
|
2929
|
+
shouldApplyIdentityRotation,
|
|
2930
|
+
keyPairToPublicBundle,
|
|
2931
|
+
buildIdentityRotationPayload,
|
|
2932
|
+
buildColdRecoveryEnrollmentPayload,
|
|
2933
|
+
signIdentityRotationPayload
|
|
2934
|
+
};
|
|
2935
|
+
}
|
|
2936
|
+
});
|
|
2937
|
+
|
|
2447
2938
|
// src/security/message-security-service.js
|
|
2448
2939
|
var require_message_security_service = __commonJS({
|
|
2449
2940
|
"src/security/message-security-service.js"(exports, module) {
|
|
2450
2941
|
var nacl = require_nacl_fast();
|
|
2451
2942
|
var naclUtil = require_nacl_util();
|
|
2452
2943
|
var VDF = require_vdf();
|
|
2944
|
+
var DEFAULT_APP_PASSWORD = "change-this-app-password";
|
|
2453
2945
|
var DEFAULT_SECURITY_OPTIONS = {
|
|
2454
2946
|
enabled: true,
|
|
2455
2947
|
signingEnabled: true,
|
|
2456
2948
|
encryptionEnabled: true,
|
|
2457
2949
|
powEnabled: true,
|
|
2458
2950
|
powTargetMs: 1e3,
|
|
2459
|
-
appPassword:
|
|
2951
|
+
appPassword: DEFAULT_APP_PASSWORD,
|
|
2460
2952
|
broadcastPasswords: {},
|
|
2461
2953
|
resolveBroadcastPassword: null,
|
|
2462
2954
|
powSteps: 22,
|
|
@@ -2551,16 +3043,85 @@ var require_message_security_service = __commonJS({
|
|
|
2551
3043
|
encryptionPublicKey: naclUtil.encodeBase64(this.encryptionPublicKey)
|
|
2552
3044
|
};
|
|
2553
3045
|
this.peerPublicKeys = /* @__PURE__ */ new Map();
|
|
3046
|
+
this.peerIdentityGenerations = /* @__PURE__ */ new Map();
|
|
3047
|
+
this.peerRecoveryPublicKeys = /* @__PURE__ */ new Map();
|
|
2554
3048
|
for (const [peerId, peerKey] of Object.entries(this.options.trustedPeerKeys || {})) {
|
|
2555
3049
|
this.peerPublicKeys.set(peerId, normalizePeerPublicKey(peerKey));
|
|
3050
|
+
this.peerIdentityGenerations.set(peerId, 1);
|
|
2556
3051
|
}
|
|
2557
3052
|
this.calibratedPowSteps = this.options.powSteps;
|
|
2558
3053
|
}
|
|
2559
3054
|
getPublicKey() {
|
|
2560
3055
|
return { ...this.publicKeyBundle };
|
|
2561
3056
|
}
|
|
2562
|
-
registerPeerPublicKey(peerId, publicKey) {
|
|
2563
|
-
|
|
3057
|
+
registerPeerPublicKey(peerId, publicKey, options = {}) {
|
|
3058
|
+
const normalized = normalizePeerPublicKey(publicKey);
|
|
3059
|
+
const generation = typeof options.generation === "number" ? options.generation : 1;
|
|
3060
|
+
const currentGeneration = this.peerIdentityGenerations.get(peerId) || 0;
|
|
3061
|
+
if (generation < currentGeneration && options.allowDowngrade !== true) {
|
|
3062
|
+
throw new Error(`Refusing older identity generation for peer ${peerId}`);
|
|
3063
|
+
}
|
|
3064
|
+
this.peerPublicKeys.set(peerId, normalized);
|
|
3065
|
+
this.peerIdentityGenerations.set(peerId, Math.max(currentGeneration, generation));
|
|
3066
|
+
}
|
|
3067
|
+
getPeerIdentityGeneration(peerId) {
|
|
3068
|
+
return this.peerIdentityGenerations.get(peerId) || 0;
|
|
3069
|
+
}
|
|
3070
|
+
getPeerIdentityState(peerId) {
|
|
3071
|
+
const publicKey = this.peerPublicKeys.get(peerId);
|
|
3072
|
+
const recoveryPublicKey = this.peerRecoveryPublicKeys.get(peerId) || null;
|
|
3073
|
+
if (!publicKey && !recoveryPublicKey) {
|
|
3074
|
+
return null;
|
|
3075
|
+
}
|
|
3076
|
+
return {
|
|
3077
|
+
publicKey: publicKey ? { ...publicKey } : null,
|
|
3078
|
+
generation: this.getPeerIdentityGeneration(peerId),
|
|
3079
|
+
recoveryPublicKey
|
|
3080
|
+
};
|
|
3081
|
+
}
|
|
3082
|
+
registerPeerRecoveryPublicKey(peerId, recoveryPublicKey) {
|
|
3083
|
+
if (!peerId || !recoveryPublicKey) {
|
|
3084
|
+
throw new Error("registerPeerRecoveryPublicKey requires peerId and recoveryPublicKey");
|
|
3085
|
+
}
|
|
3086
|
+
this.peerRecoveryPublicKeys.set(peerId, recoveryPublicKey);
|
|
3087
|
+
}
|
|
3088
|
+
getPeerRecoveryPublicKey(peerId) {
|
|
3089
|
+
return this.peerRecoveryPublicKeys.get(peerId) || null;
|
|
3090
|
+
}
|
|
3091
|
+
applyColdRecoveryEnrollment(peerId, enrollment) {
|
|
3092
|
+
const { verifyColdRecoveryEnrollment } = require_identity_rotation();
|
|
3093
|
+
const verified = verifyColdRecoveryEnrollment(enrollment);
|
|
3094
|
+
if (!verified.ok) {
|
|
3095
|
+
const error = new Error(`Invalid cold recovery enrollment: ${verified.error}`);
|
|
3096
|
+
error.code = "INVALID_COLD_RECOVERY_ENROLLMENT";
|
|
3097
|
+
throw error;
|
|
3098
|
+
}
|
|
3099
|
+
this.registerPeerRecoveryPublicKey(peerId, enrollment.recoveryPublicKey);
|
|
3100
|
+
return { applied: true, recoveryPublicKey: enrollment.recoveryPublicKey };
|
|
3101
|
+
}
|
|
3102
|
+
applyIdentityRotation(peerId, rotation) {
|
|
3103
|
+
const { shouldApplyIdentityRotation } = require_identity_rotation();
|
|
3104
|
+
const currentState = this.getPeerIdentityState(peerId);
|
|
3105
|
+
const decision = shouldApplyIdentityRotation(currentState, rotation, {
|
|
3106
|
+
enrolledRecoveryPublicKey: this.getPeerRecoveryPublicKey(peerId)
|
|
3107
|
+
});
|
|
3108
|
+
if (!decision.apply) {
|
|
3109
|
+
if (decision.reason && decision.reason !== "stale-generation" && decision.reason !== "previous-key-mismatch") {
|
|
3110
|
+
const error = new Error(`Invalid identity rotation: ${decision.reason}`);
|
|
3111
|
+
error.code = "INVALID_IDENTITY_ROTATION";
|
|
3112
|
+
throw error;
|
|
3113
|
+
}
|
|
3114
|
+
return { applied: false, reason: decision.reason };
|
|
3115
|
+
}
|
|
3116
|
+
this.registerPeerPublicKey(peerId, rotation.nextPublicKey, {
|
|
3117
|
+
generation: rotation.toGeneration
|
|
3118
|
+
});
|
|
3119
|
+
return {
|
|
3120
|
+
applied: true,
|
|
3121
|
+
fromGeneration: rotation.fromGeneration,
|
|
3122
|
+
toGeneration: rotation.toGeneration,
|
|
3123
|
+
rotationKind: rotation.rotationKind
|
|
3124
|
+
};
|
|
2564
3125
|
}
|
|
2565
3126
|
resolvePeerPublicKey(peerId, fallbackPublicKey) {
|
|
2566
3127
|
const trusted = this.peerPublicKeys.get(peerId);
|
|
@@ -2875,7 +3436,8 @@ var require_message_security_service = __commonJS({
|
|
|
2875
3436
|
stableStringify,
|
|
2876
3437
|
deriveBroadcastKey,
|
|
2877
3438
|
legacyBroadcastKey,
|
|
2878
|
-
DEFAULT_SECURITY_OPTIONS
|
|
3439
|
+
DEFAULT_SECURITY_OPTIONS,
|
|
3440
|
+
DEFAULT_APP_PASSWORD
|
|
2879
3441
|
};
|
|
2880
3442
|
}
|
|
2881
3443
|
});
|
|
@@ -2945,7 +3507,17 @@ var require_dignity_p2p = __commonJS({
|
|
|
2945
3507
|
var nacl = require_nacl_fast();
|
|
2946
3508
|
var naclUtil = require_nacl_util();
|
|
2947
3509
|
var EventEmitter = require_event_emitter();
|
|
2948
|
-
var {
|
|
3510
|
+
var {
|
|
3511
|
+
MessageSecurityService,
|
|
3512
|
+
stableStringify,
|
|
3513
|
+
DEFAULT_APP_PASSWORD
|
|
3514
|
+
} = require_message_security_service();
|
|
3515
|
+
var {
|
|
3516
|
+
revokeAndRotateIdentity,
|
|
3517
|
+
rotateIdentityPassword,
|
|
3518
|
+
enrollColdRecoveryPassword
|
|
3519
|
+
} = require_identity_rotation();
|
|
3520
|
+
var { deriveKeyPairFromCredentials } = require_derive_key_pair();
|
|
2949
3521
|
var {
|
|
2950
3522
|
DEFAULT_PEER_GROUP_OPTIONS,
|
|
2951
3523
|
peerGroupScope,
|
|
@@ -2990,6 +3562,9 @@ var require_dignity_p2p = __commonJS({
|
|
|
2990
3562
|
this.defaultGossipMaxHops = security && typeof security.gossipMaxHops === "number" ? security.gossipMaxHops : DEFAULT_PEER_GROUP_OPTIONS.maxHops;
|
|
2991
3563
|
this.globalMaxOpenConnections = security && typeof security.globalMaxOpenConnections === "number" ? security.globalMaxOpenConnections : 32;
|
|
2992
3564
|
this.gossipIdTtlMs = security && typeof security.gossipIdTtlMs === "number" ? security.gossipIdTtlMs : 5 * 60 * 1e3;
|
|
3565
|
+
this.maxSeenGossipIds = security && typeof security.maxSeenGossipIds === "number" ? security.maxSeenGossipIds : 1e5;
|
|
3566
|
+
this.gossipPublishMinIntervalMs = security && typeof security.gossipPublishMinIntervalMs === "number" ? security.gossipPublishMinIntervalMs : 0;
|
|
3567
|
+
this.lastGossipPublishAt = /* @__PURE__ */ new Map();
|
|
2993
3568
|
this.maxAppliedOperations = security && typeof security.maxAppliedOperations === "number" ? security.maxAppliedOperations : 5e4;
|
|
2994
3569
|
this.state = /* @__PURE__ */ new Map();
|
|
2995
3570
|
this.appliedOperations = /* @__PURE__ */ new Map();
|
|
@@ -2998,6 +3573,13 @@ var require_dignity_p2p = __commonJS({
|
|
|
2998
3573
|
async start() {
|
|
2999
3574
|
this.networkAdapter.onMessage(this.boundMessageHandler);
|
|
3000
3575
|
await this.networkAdapter.start(this.nodeId);
|
|
3576
|
+
const appPassword = this.securityService.options.appPassword;
|
|
3577
|
+
if (!appPassword || appPassword === DEFAULT_APP_PASSWORD) {
|
|
3578
|
+
this.emit("warning", {
|
|
3579
|
+
type: "default-app-password",
|
|
3580
|
+
message: "Using the default appPassword is insecure; set a strong shared secret in production."
|
|
3581
|
+
});
|
|
3582
|
+
}
|
|
3001
3583
|
}
|
|
3002
3584
|
async stop() {
|
|
3003
3585
|
const joinedGroups = Array.from(this.peerGroups.keys());
|
|
@@ -3293,8 +3875,138 @@ var require_dignity_p2p = __commonJS({
|
|
|
3293
3875
|
connectToPeers: this.resolveReplicationPeers(collectionName, id, options, { fromRecord: existing })
|
|
3294
3876
|
});
|
|
3295
3877
|
}
|
|
3296
|
-
registerPeerPublicKey(peerId, publicKey) {
|
|
3297
|
-
this.securityService.registerPeerPublicKey(peerId, publicKey);
|
|
3878
|
+
registerPeerPublicKey(peerId, publicKey, options = {}) {
|
|
3879
|
+
this.securityService.registerPeerPublicKey(peerId, publicKey, options);
|
|
3880
|
+
}
|
|
3881
|
+
getPeerIdentityGeneration(peerId) {
|
|
3882
|
+
return this.securityService.getPeerIdentityGeneration(peerId);
|
|
3883
|
+
}
|
|
3884
|
+
getPeerIdentityState(peerId) {
|
|
3885
|
+
return this.securityService.getPeerIdentityState(peerId);
|
|
3886
|
+
}
|
|
3887
|
+
applyPeerIdentityRotation(peerId, rotation) {
|
|
3888
|
+
const result = this.securityService.applyIdentityRotation(peerId, rotation);
|
|
3889
|
+
if (result.applied) {
|
|
3890
|
+
this.emit("identityrotated", {
|
|
3891
|
+
peerId,
|
|
3892
|
+
username: rotation.username,
|
|
3893
|
+
fromGeneration: result.fromGeneration,
|
|
3894
|
+
toGeneration: result.toGeneration,
|
|
3895
|
+
rotationKind: result.rotationKind
|
|
3896
|
+
});
|
|
3897
|
+
}
|
|
3898
|
+
return result;
|
|
3899
|
+
}
|
|
3900
|
+
async broadcastIdentityRotation(rotation, options = {}) {
|
|
3901
|
+
return this.broadcastMessage("identity:rotate", rotation, options);
|
|
3902
|
+
}
|
|
3903
|
+
async broadcastColdRecoveryEnrollment(enrollment, options = {}) {
|
|
3904
|
+
return this.broadcastMessage("identity:cold-enroll", enrollment, options);
|
|
3905
|
+
}
|
|
3906
|
+
applyPeerColdRecoveryEnrollment(peerId, enrollment) {
|
|
3907
|
+
const result = this.securityService.applyColdRecoveryEnrollment(peerId, enrollment);
|
|
3908
|
+
if (result.applied) {
|
|
3909
|
+
this.emit("coldrecoveryenrolled", {
|
|
3910
|
+
peerId,
|
|
3911
|
+
username: enrollment.username,
|
|
3912
|
+
recoveryPublicKey: enrollment.recoveryPublicKey
|
|
3913
|
+
});
|
|
3914
|
+
}
|
|
3915
|
+
return result;
|
|
3916
|
+
}
|
|
3917
|
+
async enrollAndBroadcastColdRecovery({
|
|
3918
|
+
username,
|
|
3919
|
+
coldPassword,
|
|
3920
|
+
pepper = "",
|
|
3921
|
+
kdfIterations,
|
|
3922
|
+
broadcastOptions = {}
|
|
3923
|
+
} = {}) {
|
|
3924
|
+
const result = await enrollColdRecoveryPassword({
|
|
3925
|
+
username,
|
|
3926
|
+
coldPassword,
|
|
3927
|
+
pepper,
|
|
3928
|
+
kdfIterations
|
|
3929
|
+
});
|
|
3930
|
+
await this.broadcastColdRecoveryEnrollment(result.enrollment, broadcastOptions);
|
|
3931
|
+
return result;
|
|
3932
|
+
}
|
|
3933
|
+
async revokeAndRotateDerivedIdentity({
|
|
3934
|
+
username,
|
|
3935
|
+
password,
|
|
3936
|
+
coldPassword,
|
|
3937
|
+
currentGeneration = 1,
|
|
3938
|
+
reason = "compromise-recovery",
|
|
3939
|
+
pepper = "",
|
|
3940
|
+
kdfIterations,
|
|
3941
|
+
broadcast = false,
|
|
3942
|
+
broadcastOptions = {}
|
|
3943
|
+
} = {}) {
|
|
3944
|
+
const result = await revokeAndRotateIdentity({
|
|
3945
|
+
username,
|
|
3946
|
+
password,
|
|
3947
|
+
coldPassword,
|
|
3948
|
+
currentGeneration,
|
|
3949
|
+
reason,
|
|
3950
|
+
pepper,
|
|
3951
|
+
kdfIterations
|
|
3952
|
+
});
|
|
3953
|
+
if (broadcast) {
|
|
3954
|
+
await this.broadcastIdentityRotation(result.rotation, broadcastOptions);
|
|
3955
|
+
}
|
|
3956
|
+
return result;
|
|
3957
|
+
}
|
|
3958
|
+
async rotateDerivedIdentityPassword({
|
|
3959
|
+
username,
|
|
3960
|
+
currentPassword,
|
|
3961
|
+
newPassword,
|
|
3962
|
+
coldPassword,
|
|
3963
|
+
currentGeneration = 1,
|
|
3964
|
+
reason = "password-change",
|
|
3965
|
+
pepper = "",
|
|
3966
|
+
kdfIterations,
|
|
3967
|
+
broadcast = false,
|
|
3968
|
+
broadcastOptions = {}
|
|
3969
|
+
} = {}) {
|
|
3970
|
+
const result = await rotateIdentityPassword({
|
|
3971
|
+
username,
|
|
3972
|
+
currentPassword,
|
|
3973
|
+
newPassword,
|
|
3974
|
+
coldPassword,
|
|
3975
|
+
currentGeneration,
|
|
3976
|
+
reason,
|
|
3977
|
+
pepper,
|
|
3978
|
+
kdfIterations
|
|
3979
|
+
});
|
|
3980
|
+
if (broadcast) {
|
|
3981
|
+
await this.broadcastIdentityRotation(result.rotation, broadcastOptions);
|
|
3982
|
+
}
|
|
3983
|
+
return result;
|
|
3984
|
+
}
|
|
3985
|
+
async adoptDerivedIdentityKeyPair(keyPair, { generation = 1 } = {}) {
|
|
3986
|
+
if (!keyPair || !keyPair.signing || !keyPair.encryption) {
|
|
3987
|
+
throw new Error("adoptDerivedIdentityKeyPair requires a derived keyPair");
|
|
3988
|
+
}
|
|
3989
|
+
this.securityService.signingSecretKey = keyPair.signing.secretKey;
|
|
3990
|
+
this.securityService.signingPublicKey = keyPair.signing.publicKey;
|
|
3991
|
+
this.securityService.encryptionSecretKey = keyPair.encryption.secretKey;
|
|
3992
|
+
this.securityService.encryptionPublicKey = keyPair.encryption.publicKey;
|
|
3993
|
+
this.securityService.publicKeyBundle = {
|
|
3994
|
+
signingPublicKey: naclUtil.encodeBase64(keyPair.signing.publicKey),
|
|
3995
|
+
encryptionPublicKey: naclUtil.encodeBase64(keyPair.encryption.publicKey)
|
|
3996
|
+
};
|
|
3997
|
+
this.securityService.options.keyPair = keyPair;
|
|
3998
|
+
this.securityService.options.identityGeneration = generation;
|
|
3999
|
+
}
|
|
4000
|
+
async deriveAndAdoptIdentity({ username, password, generation = 1, pepper = "", kdfIterations } = {}) {
|
|
4001
|
+
const keyPair = await deriveKeyPairFromCredentials({
|
|
4002
|
+
username,
|
|
4003
|
+
password,
|
|
4004
|
+
generation,
|
|
4005
|
+
pepper,
|
|
4006
|
+
kdfIterations
|
|
4007
|
+
});
|
|
4008
|
+
await this.adoptDerivedIdentityKeyPair(keyPair, { generation });
|
|
4009
|
+
return keyPair;
|
|
3298
4010
|
}
|
|
3299
4011
|
trustPeerPublicKey(peerId, publicKey) {
|
|
3300
4012
|
if (!peerId || !publicKey) {
|
|
@@ -3416,6 +4128,13 @@ var require_dignity_p2p = __commonJS({
|
|
|
3416
4128
|
this.seenGossipIds.delete(gossipId);
|
|
3417
4129
|
}
|
|
3418
4130
|
}
|
|
4131
|
+
while (this.seenGossipIds.size > this.maxSeenGossipIds) {
|
|
4132
|
+
const oldestGossipId = this.seenGossipIds.keys().next().value;
|
|
4133
|
+
if (!oldestGossipId) {
|
|
4134
|
+
break;
|
|
4135
|
+
}
|
|
4136
|
+
this.seenGossipIds.delete(oldestGossipId);
|
|
4137
|
+
}
|
|
3419
4138
|
}
|
|
3420
4139
|
hasSeenGossip(gossipId) {
|
|
3421
4140
|
if (!gossipId) {
|
|
@@ -3429,6 +4148,7 @@ var require_dignity_p2p = __commonJS({
|
|
|
3429
4148
|
return;
|
|
3430
4149
|
}
|
|
3431
4150
|
this.seenGossipIds.set(gossipId, this.now() + this.gossipIdTtlMs);
|
|
4151
|
+
this.pruneSeenGossip();
|
|
3432
4152
|
}
|
|
3433
4153
|
listConnectedPeerIds() {
|
|
3434
4154
|
if (typeof this.networkAdapter.listOpenPeerIds === "function") {
|
|
@@ -3506,6 +4226,15 @@ var require_dignity_p2p = __commonJS({
|
|
|
3506
4226
|
if (!group && options.allowUnjoined !== true) {
|
|
3507
4227
|
throw new Error(`PeerGroup ${groupId} has not been joined`);
|
|
3508
4228
|
}
|
|
4229
|
+
if (this.gossipPublishMinIntervalMs > 0) {
|
|
4230
|
+
const lastPublishAt = this.lastGossipPublishAt.get(groupId) || 0;
|
|
4231
|
+
const elapsed = this.now() - lastPublishAt;
|
|
4232
|
+
if (elapsed < this.gossipPublishMinIntervalMs) {
|
|
4233
|
+
const error = new Error(`Gossip publish rate limit exceeded for group ${groupId}`);
|
|
4234
|
+
error.code = "GOSSIP_RATE_LIMIT";
|
|
4235
|
+
throw error;
|
|
4236
|
+
}
|
|
4237
|
+
}
|
|
3509
4238
|
const fanout = typeof options.fanout === "number" ? options.fanout : group ? group.fanout : this.defaultPeerGroupFanout;
|
|
3510
4239
|
const maxActivePeers = group ? group.maxActivePeers : this.defaultPeerGroupMaxActivePeers;
|
|
3511
4240
|
const maxHop = typeof options.maxHops === "number" ? options.maxHops : group ? group.maxHops : this.defaultGossipMaxHops;
|
|
@@ -3516,6 +4245,7 @@ var require_dignity_p2p = __commonJS({
|
|
|
3516
4245
|
}
|
|
3517
4246
|
const gossipId = options.gossipId || this.idGenerator();
|
|
3518
4247
|
this.markSeenGossip(gossipId);
|
|
4248
|
+
this.lastGossipPublishAt.set(groupId, this.now());
|
|
3519
4249
|
await this.broadcastMessage("peer-group:gossip", {
|
|
3520
4250
|
groupId,
|
|
3521
4251
|
gossipId,
|
|
@@ -3822,6 +4552,35 @@ var require_dignity_p2p = __commonJS({
|
|
|
3822
4552
|
if (!decrypted || decrypted.ignored) {
|
|
3823
4553
|
return;
|
|
3824
4554
|
}
|
|
4555
|
+
if (decrypted.messageType === "identity:rotate") {
|
|
4556
|
+
const peerId = decrypted.senderId || decrypted.payload?.username;
|
|
4557
|
+
if (peerId && decrypted.payload) {
|
|
4558
|
+
const result = this.applyPeerIdentityRotation(peerId, decrypted.payload);
|
|
4559
|
+
if (!result.applied) {
|
|
4560
|
+
this.emit("warning", {
|
|
4561
|
+
type: "identity-rotation-ignored",
|
|
4562
|
+
peerId,
|
|
4563
|
+
reason: result.reason
|
|
4564
|
+
});
|
|
4565
|
+
}
|
|
4566
|
+
}
|
|
4567
|
+
return;
|
|
4568
|
+
}
|
|
4569
|
+
if (decrypted.messageType === "identity:cold-enroll") {
|
|
4570
|
+
const peerId = decrypted.senderId || decrypted.payload?.username;
|
|
4571
|
+
if (peerId && decrypted.payload) {
|
|
4572
|
+
try {
|
|
4573
|
+
this.applyPeerColdRecoveryEnrollment(peerId, decrypted.payload);
|
|
4574
|
+
} catch (error) {
|
|
4575
|
+
this.emit("warning", {
|
|
4576
|
+
type: "cold-recovery-enrollment-rejected",
|
|
4577
|
+
peerId,
|
|
4578
|
+
error
|
|
4579
|
+
});
|
|
4580
|
+
}
|
|
4581
|
+
}
|
|
4582
|
+
return;
|
|
4583
|
+
}
|
|
3825
4584
|
if (message && message.senderId && message.senderPublicKey) {
|
|
3826
4585
|
this.trustPeerPublicKey(message.senderId, message.senderPublicKey);
|
|
3827
4586
|
}
|
|
@@ -3833,7 +4592,10 @@ var require_dignity_p2p = __commonJS({
|
|
|
3833
4592
|
const payload = decrypted.payload || {};
|
|
3834
4593
|
const { collectionName, record } = payload;
|
|
3835
4594
|
if (collectionName && record) {
|
|
3836
|
-
const applied = this.restoreRecord(collectionName, record
|
|
4595
|
+
const applied = this.restoreRecord(collectionName, record, {
|
|
4596
|
+
rejectOnHashMismatch: true,
|
|
4597
|
+
via: "direct-mesh"
|
|
4598
|
+
});
|
|
3837
4599
|
if (applied) {
|
|
3838
4600
|
this.emit("change", {
|
|
3839
4601
|
kind: "snapshot",
|
|
@@ -11705,8 +12467,20 @@ var require_index = __commonJS({
|
|
|
11705
12467
|
var SlothPermutation = require_sloth_vdf();
|
|
11706
12468
|
var {
|
|
11707
12469
|
MessageSecurityService,
|
|
11708
|
-
DEFAULT_SECURITY_OPTIONS
|
|
12470
|
+
DEFAULT_SECURITY_OPTIONS,
|
|
12471
|
+
DEFAULT_APP_PASSWORD
|
|
11709
12472
|
} = require_message_security_service();
|
|
12473
|
+
var { deriveKeyPairFromCredentials, keyPairToPublicBundle, deriveColdRecoverySigningKey } = require_derive_key_pair();
|
|
12474
|
+
var {
|
|
12475
|
+
createIdentityRotation,
|
|
12476
|
+
verifyIdentityRotation,
|
|
12477
|
+
revokeAndRotateIdentity,
|
|
12478
|
+
rotateIdentityPassword,
|
|
12479
|
+
enrollColdRecoveryPassword,
|
|
12480
|
+
verifyColdRecoveryEnrollment,
|
|
12481
|
+
shouldApplyIdentityRotation
|
|
12482
|
+
} = require_identity_rotation();
|
|
12483
|
+
var parsePeerJsServerUrl = require_parse_peerjs_url();
|
|
11710
12484
|
var {
|
|
11711
12485
|
PEER_GROUP_SCOPE_PREFIX,
|
|
11712
12486
|
DEFAULT_PEER_GROUP_OPTIONS,
|
|
@@ -11731,6 +12505,18 @@ var require_index = __commonJS({
|
|
|
11731
12505
|
SlothPermutation,
|
|
11732
12506
|
MessageSecurityService,
|
|
11733
12507
|
DEFAULT_SECURITY_OPTIONS,
|
|
12508
|
+
DEFAULT_APP_PASSWORD,
|
|
12509
|
+
deriveKeyPairFromCredentials,
|
|
12510
|
+
deriveColdRecoverySigningKey,
|
|
12511
|
+
keyPairToPublicBundle,
|
|
12512
|
+
createIdentityRotation,
|
|
12513
|
+
verifyIdentityRotation,
|
|
12514
|
+
revokeAndRotateIdentity,
|
|
12515
|
+
rotateIdentityPassword,
|
|
12516
|
+
enrollColdRecoveryPassword,
|
|
12517
|
+
verifyColdRecoveryEnrollment,
|
|
12518
|
+
shouldApplyIdentityRotation,
|
|
12519
|
+
parsePeerJsServerUrl,
|
|
11734
12520
|
PEER_GROUP_SCOPE_PREFIX,
|
|
11735
12521
|
DEFAULT_PEER_GROUP_OPTIONS,
|
|
11736
12522
|
peerGroupScope,
|