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.cjs.js
CHANGED
|
@@ -2424,19 +2424,511 @@ var require_vdf = __commonJS({
|
|
|
2424
2424
|
}
|
|
2425
2425
|
});
|
|
2426
2426
|
|
|
2427
|
+
// src/security/derive-key-pair.js
|
|
2428
|
+
var require_derive_key_pair = __commonJS({
|
|
2429
|
+
"src/security/derive-key-pair.js"(exports2, module2) {
|
|
2430
|
+
var nacl = require_nacl_fast();
|
|
2431
|
+
var naclUtil = require_nacl_util();
|
|
2432
|
+
var { deriveBroadcastKey, DEFAULT_SECURITY_OPTIONS: DEFAULT_SECURITY_OPTIONS2 } = require_message_security_service();
|
|
2433
|
+
var SIGNING_INFO = "dignity-signing-v1";
|
|
2434
|
+
var ENCRYPTION_INFO = "dignity-encryption-v1";
|
|
2435
|
+
var COLD_RECOVERY_INFO = "dignity-cold-recovery-v1";
|
|
2436
|
+
function utf8ToBytes(value) {
|
|
2437
|
+
return naclUtil.decodeUTF8(value);
|
|
2438
|
+
}
|
|
2439
|
+
function concatBytes(...parts) {
|
|
2440
|
+
const total = parts.reduce((sum, part) => sum + part.length, 0);
|
|
2441
|
+
const result = new Uint8Array(total);
|
|
2442
|
+
let offset = 0;
|
|
2443
|
+
for (const part of parts) {
|
|
2444
|
+
result.set(part, offset);
|
|
2445
|
+
offset += part.length;
|
|
2446
|
+
}
|
|
2447
|
+
return result;
|
|
2448
|
+
}
|
|
2449
|
+
function buildColdRecoverySalt(username, pepper = "") {
|
|
2450
|
+
if (!username || typeof username !== "string") {
|
|
2451
|
+
throw new Error("deriveColdRecoverySigningKey requires username");
|
|
2452
|
+
}
|
|
2453
|
+
const segments = ["dignity-cold-recovery-v1"];
|
|
2454
|
+
if (pepper) {
|
|
2455
|
+
segments.push(pepper);
|
|
2456
|
+
}
|
|
2457
|
+
segments.push(username, COLD_RECOVERY_INFO);
|
|
2458
|
+
return utf8ToBytes(segments.join("\0"));
|
|
2459
|
+
}
|
|
2460
|
+
async function deriveColdRecoverySigningKey2({
|
|
2461
|
+
username,
|
|
2462
|
+
coldPassword,
|
|
2463
|
+
pepper = "",
|
|
2464
|
+
kdfIterations
|
|
2465
|
+
} = {}) {
|
|
2466
|
+
if (!coldPassword || typeof coldPassword !== "string") {
|
|
2467
|
+
throw new Error("deriveColdRecoverySigningKey requires coldPassword");
|
|
2468
|
+
}
|
|
2469
|
+
const salt = buildColdRecoverySalt(username, pepper);
|
|
2470
|
+
const iterations = typeof kdfIterations === "number" ? kdfIterations : DEFAULT_SECURITY_OPTIONS2.kdfIterations;
|
|
2471
|
+
const seed = await deriveBroadcastKey(coldPassword, salt, iterations);
|
|
2472
|
+
const signing = nacl.sign.keyPair.fromSeed(seed);
|
|
2473
|
+
return {
|
|
2474
|
+
signing,
|
|
2475
|
+
recoveryPublicKey: naclUtil.encodeBase64(signing.publicKey)
|
|
2476
|
+
};
|
|
2477
|
+
}
|
|
2478
|
+
function buildIdentitySalt(username, info, pepper = "", generation = 1) {
|
|
2479
|
+
if (!username || typeof username !== "string") {
|
|
2480
|
+
throw new Error("deriveKeyPairFromCredentials requires username");
|
|
2481
|
+
}
|
|
2482
|
+
if (!info || typeof info !== "string") {
|
|
2483
|
+
throw new Error("deriveKeyPairFromCredentials requires info label");
|
|
2484
|
+
}
|
|
2485
|
+
const normalizedGeneration = Number(generation);
|
|
2486
|
+
if (!Number.isInteger(normalizedGeneration) || normalizedGeneration < 1) {
|
|
2487
|
+
throw new Error("deriveKeyPairFromCredentials requires generation >= 1");
|
|
2488
|
+
}
|
|
2489
|
+
const segments = ["dignity-identity-v1"];
|
|
2490
|
+
if (pepper) {
|
|
2491
|
+
segments.push(pepper);
|
|
2492
|
+
}
|
|
2493
|
+
segments.push(username, `gen:${normalizedGeneration}`, info);
|
|
2494
|
+
return utf8ToBytes(segments.join("\0"));
|
|
2495
|
+
}
|
|
2496
|
+
async function deriveIdentitySeed({ password, username, info, pepper, generation, kdfIterations }) {
|
|
2497
|
+
if (!password || typeof password !== "string") {
|
|
2498
|
+
throw new Error("deriveKeyPairFromCredentials requires password");
|
|
2499
|
+
}
|
|
2500
|
+
const salt = buildIdentitySalt(username, info, pepper, generation);
|
|
2501
|
+
const iterations = typeof kdfIterations === "number" ? kdfIterations : DEFAULT_SECURITY_OPTIONS2.kdfIterations;
|
|
2502
|
+
return deriveBroadcastKey(password, salt, iterations);
|
|
2503
|
+
}
|
|
2504
|
+
async function deriveKeyPairFromCredentials2({
|
|
2505
|
+
username,
|
|
2506
|
+
password,
|
|
2507
|
+
pepper = "",
|
|
2508
|
+
generation = 1,
|
|
2509
|
+
kdfIterations
|
|
2510
|
+
} = {}) {
|
|
2511
|
+
const signingSeed = await deriveIdentitySeed({
|
|
2512
|
+
password,
|
|
2513
|
+
username,
|
|
2514
|
+
info: SIGNING_INFO,
|
|
2515
|
+
pepper,
|
|
2516
|
+
generation,
|
|
2517
|
+
kdfIterations
|
|
2518
|
+
});
|
|
2519
|
+
const encryptionSecret = await deriveIdentitySeed({
|
|
2520
|
+
password,
|
|
2521
|
+
username,
|
|
2522
|
+
info: ENCRYPTION_INFO,
|
|
2523
|
+
pepper,
|
|
2524
|
+
generation,
|
|
2525
|
+
kdfIterations
|
|
2526
|
+
});
|
|
2527
|
+
return {
|
|
2528
|
+
signing: nacl.sign.keyPair.fromSeed(signingSeed),
|
|
2529
|
+
encryption: nacl.box.keyPair.fromSecretKey(encryptionSecret),
|
|
2530
|
+
generation
|
|
2531
|
+
};
|
|
2532
|
+
}
|
|
2533
|
+
function keyPairToPublicBundle2(keyPair) {
|
|
2534
|
+
return {
|
|
2535
|
+
signingPublicKey: naclUtil.encodeBase64(keyPair.signing.publicKey),
|
|
2536
|
+
encryptionPublicKey: naclUtil.encodeBase64(keyPair.encryption.publicKey)
|
|
2537
|
+
};
|
|
2538
|
+
}
|
|
2539
|
+
module2.exports = {
|
|
2540
|
+
deriveKeyPairFromCredentials: deriveKeyPairFromCredentials2,
|
|
2541
|
+
deriveColdRecoverySigningKey: deriveColdRecoverySigningKey2,
|
|
2542
|
+
keyPairToPublicBundle: keyPairToPublicBundle2,
|
|
2543
|
+
buildIdentitySalt,
|
|
2544
|
+
buildColdRecoverySalt,
|
|
2545
|
+
SIGNING_INFO,
|
|
2546
|
+
ENCRYPTION_INFO,
|
|
2547
|
+
COLD_RECOVERY_INFO,
|
|
2548
|
+
concatBytes
|
|
2549
|
+
};
|
|
2550
|
+
}
|
|
2551
|
+
});
|
|
2552
|
+
|
|
2553
|
+
// src/security/identity-rotation.js
|
|
2554
|
+
var require_identity_rotation = __commonJS({
|
|
2555
|
+
"src/security/identity-rotation.js"(exports2, module2) {
|
|
2556
|
+
var nacl = require_nacl_fast();
|
|
2557
|
+
var naclUtil = require_nacl_util();
|
|
2558
|
+
var { stableStringify } = require_message_security_service();
|
|
2559
|
+
var {
|
|
2560
|
+
deriveKeyPairFromCredentials: deriveKeyPairFromCredentials2,
|
|
2561
|
+
deriveColdRecoverySigningKey: deriveColdRecoverySigningKey2,
|
|
2562
|
+
keyPairToPublicBundle: keyPairToPublicBundle2
|
|
2563
|
+
} = require_derive_key_pair();
|
|
2564
|
+
var ROTATION_TYPES = /* @__PURE__ */ new Set(["compromise-recovery", "password-change"]);
|
|
2565
|
+
function utf8ToBytes(value) {
|
|
2566
|
+
return naclUtil.decodeUTF8(value);
|
|
2567
|
+
}
|
|
2568
|
+
function normalizePublicKeyBundle(publicKey) {
|
|
2569
|
+
if (!publicKey || !publicKey.signingPublicKey || !publicKey.encryptionPublicKey) {
|
|
2570
|
+
throw new Error("Public key bundle requires signingPublicKey and encryptionPublicKey");
|
|
2571
|
+
}
|
|
2572
|
+
return {
|
|
2573
|
+
signingPublicKey: publicKey.signingPublicKey,
|
|
2574
|
+
encryptionPublicKey: publicKey.encryptionPublicKey
|
|
2575
|
+
};
|
|
2576
|
+
}
|
|
2577
|
+
function buildIdentityRotationPayload({
|
|
2578
|
+
username,
|
|
2579
|
+
fromGeneration,
|
|
2580
|
+
toGeneration,
|
|
2581
|
+
previousPublicKey,
|
|
2582
|
+
nextPublicKey,
|
|
2583
|
+
rotationKind,
|
|
2584
|
+
reason,
|
|
2585
|
+
timestamp
|
|
2586
|
+
}) {
|
|
2587
|
+
if (!username) {
|
|
2588
|
+
throw new Error("Identity rotation requires username");
|
|
2589
|
+
}
|
|
2590
|
+
if (!ROTATION_TYPES.has(rotationKind)) {
|
|
2591
|
+
throw new Error(`Identity rotation kind must be one of: ${[...ROTATION_TYPES].join(", ")}`);
|
|
2592
|
+
}
|
|
2593
|
+
if (toGeneration !== fromGeneration + 1) {
|
|
2594
|
+
throw new Error("Identity rotation must advance generation by exactly 1");
|
|
2595
|
+
}
|
|
2596
|
+
return {
|
|
2597
|
+
version: 1,
|
|
2598
|
+
type: "identity:rotate",
|
|
2599
|
+
username,
|
|
2600
|
+
fromGeneration,
|
|
2601
|
+
toGeneration,
|
|
2602
|
+
previousPublicKey: normalizePublicKeyBundle(previousPublicKey),
|
|
2603
|
+
nextPublicKey: normalizePublicKeyBundle(nextPublicKey),
|
|
2604
|
+
rotationKind,
|
|
2605
|
+
reason: reason || "",
|
|
2606
|
+
timestamp: typeof timestamp === "number" ? timestamp : Date.now()
|
|
2607
|
+
};
|
|
2608
|
+
}
|
|
2609
|
+
function signIdentityRotationPayload(payload, signingSecretKey) {
|
|
2610
|
+
const message = utf8ToBytes(stableStringify(payload));
|
|
2611
|
+
const signature = nacl.sign.detached(message, signingSecretKey);
|
|
2612
|
+
return naclUtil.encodeBase64(signature);
|
|
2613
|
+
}
|
|
2614
|
+
function verifyDetachedSignature(payload, signatureBase64, signingPublicKeyBase64) {
|
|
2615
|
+
const message = utf8ToBytes(stableStringify(payload));
|
|
2616
|
+
const signatureBytes = naclUtil.decodeBase64(signatureBase64);
|
|
2617
|
+
const signingPublicKey = naclUtil.decodeBase64(signingPublicKeyBase64);
|
|
2618
|
+
return nacl.sign.detached.verify(message, signatureBytes, signingPublicKey);
|
|
2619
|
+
}
|
|
2620
|
+
function createIdentityRotation2({
|
|
2621
|
+
username,
|
|
2622
|
+
fromGeneration,
|
|
2623
|
+
toGeneration,
|
|
2624
|
+
previousPublicKey,
|
|
2625
|
+
nextKeyPair,
|
|
2626
|
+
rotationKind,
|
|
2627
|
+
reason,
|
|
2628
|
+
timestamp,
|
|
2629
|
+
coldRecoverySigningSecretKey
|
|
2630
|
+
}) {
|
|
2631
|
+
if (!nextKeyPair || !nextKeyPair.signing || !nextKeyPair.signing.secretKey) {
|
|
2632
|
+
throw new Error("Identity rotation requires nextKeyPair with signing secret");
|
|
2633
|
+
}
|
|
2634
|
+
const payload = buildIdentityRotationPayload({
|
|
2635
|
+
username,
|
|
2636
|
+
fromGeneration,
|
|
2637
|
+
toGeneration,
|
|
2638
|
+
previousPublicKey,
|
|
2639
|
+
nextPublicKey: keyPairToPublicBundle2(nextKeyPair),
|
|
2640
|
+
rotationKind,
|
|
2641
|
+
reason,
|
|
2642
|
+
timestamp
|
|
2643
|
+
});
|
|
2644
|
+
const rotation = {
|
|
2645
|
+
...payload,
|
|
2646
|
+
signature: signIdentityRotationPayload(payload, nextKeyPair.signing.secretKey)
|
|
2647
|
+
};
|
|
2648
|
+
if (coldRecoverySigningSecretKey) {
|
|
2649
|
+
rotation.recoverySignature = signIdentityRotationPayload(
|
|
2650
|
+
payload,
|
|
2651
|
+
coldRecoverySigningSecretKey
|
|
2652
|
+
);
|
|
2653
|
+
}
|
|
2654
|
+
return rotation;
|
|
2655
|
+
}
|
|
2656
|
+
function buildColdRecoveryEnrollmentPayload({ username, recoveryPublicKey, timestamp }) {
|
|
2657
|
+
if (!username) {
|
|
2658
|
+
throw new Error("Cold recovery enrollment requires username");
|
|
2659
|
+
}
|
|
2660
|
+
if (!recoveryPublicKey) {
|
|
2661
|
+
throw new Error("Cold recovery enrollment requires recoveryPublicKey");
|
|
2662
|
+
}
|
|
2663
|
+
return {
|
|
2664
|
+
version: 1,
|
|
2665
|
+
type: "identity:cold-enroll",
|
|
2666
|
+
username,
|
|
2667
|
+
recoveryPublicKey,
|
|
2668
|
+
timestamp: typeof timestamp === "number" ? timestamp : Date.now()
|
|
2669
|
+
};
|
|
2670
|
+
}
|
|
2671
|
+
function createColdRecoveryEnrollment({
|
|
2672
|
+
username,
|
|
2673
|
+
coldRecoverySigningSecretKey,
|
|
2674
|
+
recoveryPublicKey,
|
|
2675
|
+
timestamp
|
|
2676
|
+
}) {
|
|
2677
|
+
if (!coldRecoverySigningSecretKey) {
|
|
2678
|
+
throw new Error("Cold recovery enrollment requires cold recovery signing secret");
|
|
2679
|
+
}
|
|
2680
|
+
if (!recoveryPublicKey) {
|
|
2681
|
+
throw new Error("Cold recovery enrollment requires recoveryPublicKey");
|
|
2682
|
+
}
|
|
2683
|
+
const payload = buildColdRecoveryEnrollmentPayload({
|
|
2684
|
+
username,
|
|
2685
|
+
recoveryPublicKey,
|
|
2686
|
+
timestamp
|
|
2687
|
+
});
|
|
2688
|
+
return {
|
|
2689
|
+
...payload,
|
|
2690
|
+
signature: signIdentityRotationPayload(payload, coldRecoverySigningSecretKey)
|
|
2691
|
+
};
|
|
2692
|
+
}
|
|
2693
|
+
function verifyColdRecoveryEnrollment2(enrollment) {
|
|
2694
|
+
if (!enrollment || enrollment.type !== "identity:cold-enroll" || enrollment.version !== 1) {
|
|
2695
|
+
return { ok: false, error: "invalid-enrollment-shape" };
|
|
2696
|
+
}
|
|
2697
|
+
if (!enrollment.signature || !enrollment.recoveryPublicKey) {
|
|
2698
|
+
return { ok: false, error: "missing-enrollment-fields" };
|
|
2699
|
+
}
|
|
2700
|
+
const { signature, ...payload } = enrollment;
|
|
2701
|
+
const verified = verifyDetachedSignature(payload, signature, enrollment.recoveryPublicKey);
|
|
2702
|
+
if (!verified) {
|
|
2703
|
+
return { ok: false, error: "invalid-enrollment-signature" };
|
|
2704
|
+
}
|
|
2705
|
+
return { ok: true, enrollment };
|
|
2706
|
+
}
|
|
2707
|
+
function verifyIdentityRotation2(rotation, options = {}) {
|
|
2708
|
+
if (!rotation || rotation.type !== "identity:rotate" || rotation.version !== 1) {
|
|
2709
|
+
return { ok: false, error: "invalid-rotation-shape" };
|
|
2710
|
+
}
|
|
2711
|
+
if (!rotation.signature || !rotation.nextPublicKey || !rotation.previousPublicKey) {
|
|
2712
|
+
return { ok: false, error: "missing-rotation-fields" };
|
|
2713
|
+
}
|
|
2714
|
+
if (rotation.toGeneration !== rotation.fromGeneration + 1) {
|
|
2715
|
+
return { ok: false, error: "invalid-generation-step" };
|
|
2716
|
+
}
|
|
2717
|
+
if (!ROTATION_TYPES.has(rotation.rotationKind)) {
|
|
2718
|
+
return { ok: false, error: "invalid-rotation-kind" };
|
|
2719
|
+
}
|
|
2720
|
+
const { signature, recoverySignature, ...payload } = rotation;
|
|
2721
|
+
const verified = verifyDetachedSignature(payload, signature, rotation.nextPublicKey.signingPublicKey);
|
|
2722
|
+
if (!verified) {
|
|
2723
|
+
return { ok: false, error: "invalid-signature" };
|
|
2724
|
+
}
|
|
2725
|
+
const requiredRecoveryPublicKey = options.requiredRecoveryPublicKey || null;
|
|
2726
|
+
if (requiredRecoveryPublicKey) {
|
|
2727
|
+
if (!recoverySignature) {
|
|
2728
|
+
return { ok: false, error: "missing-recovery-signature" };
|
|
2729
|
+
}
|
|
2730
|
+
const recoveryVerified = verifyDetachedSignature(
|
|
2731
|
+
payload,
|
|
2732
|
+
recoverySignature,
|
|
2733
|
+
requiredRecoveryPublicKey
|
|
2734
|
+
);
|
|
2735
|
+
if (!recoveryVerified) {
|
|
2736
|
+
return { ok: false, error: "invalid-recovery-signature" };
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
if (rotation.previousPublicKey.signingPublicKey === rotation.nextPublicKey.signingPublicKey) {
|
|
2740
|
+
return { ok: false, error: "unchanged-signing-key" };
|
|
2741
|
+
}
|
|
2742
|
+
return { ok: true, rotation };
|
|
2743
|
+
}
|
|
2744
|
+
async function resolveColdRecoverySigningSecretKey({
|
|
2745
|
+
username,
|
|
2746
|
+
coldPassword,
|
|
2747
|
+
pepper,
|
|
2748
|
+
kdfIterations
|
|
2749
|
+
}) {
|
|
2750
|
+
if (!coldPassword) {
|
|
2751
|
+
return null;
|
|
2752
|
+
}
|
|
2753
|
+
const coldRecovery = await deriveColdRecoverySigningKey2({
|
|
2754
|
+
username,
|
|
2755
|
+
coldPassword,
|
|
2756
|
+
pepper,
|
|
2757
|
+
kdfIterations
|
|
2758
|
+
});
|
|
2759
|
+
return coldRecovery.signing.secretKey;
|
|
2760
|
+
}
|
|
2761
|
+
async function revokeAndRotateIdentity2({
|
|
2762
|
+
username,
|
|
2763
|
+
password,
|
|
2764
|
+
coldPassword,
|
|
2765
|
+
currentGeneration = 1,
|
|
2766
|
+
reason = "compromise-recovery",
|
|
2767
|
+
pepper = "",
|
|
2768
|
+
kdfIterations,
|
|
2769
|
+
timestamp
|
|
2770
|
+
} = {}) {
|
|
2771
|
+
const currentKeyPair = await deriveKeyPairFromCredentials2({
|
|
2772
|
+
username,
|
|
2773
|
+
password,
|
|
2774
|
+
generation: currentGeneration,
|
|
2775
|
+
pepper,
|
|
2776
|
+
kdfIterations
|
|
2777
|
+
});
|
|
2778
|
+
const nextGeneration = currentGeneration + 1;
|
|
2779
|
+
const nextKeyPair = await deriveKeyPairFromCredentials2({
|
|
2780
|
+
username,
|
|
2781
|
+
password,
|
|
2782
|
+
generation: nextGeneration,
|
|
2783
|
+
pepper,
|
|
2784
|
+
kdfIterations
|
|
2785
|
+
});
|
|
2786
|
+
const coldRecoverySigningSecretKey = await resolveColdRecoverySigningSecretKey({
|
|
2787
|
+
username,
|
|
2788
|
+
coldPassword,
|
|
2789
|
+
pepper,
|
|
2790
|
+
kdfIterations
|
|
2791
|
+
});
|
|
2792
|
+
const rotation = createIdentityRotation2({
|
|
2793
|
+
username,
|
|
2794
|
+
fromGeneration: currentGeneration,
|
|
2795
|
+
toGeneration: nextGeneration,
|
|
2796
|
+
previousPublicKey: keyPairToPublicBundle2(currentKeyPair),
|
|
2797
|
+
nextKeyPair,
|
|
2798
|
+
rotationKind: "compromise-recovery",
|
|
2799
|
+
reason,
|
|
2800
|
+
timestamp,
|
|
2801
|
+
coldRecoverySigningSecretKey
|
|
2802
|
+
});
|
|
2803
|
+
return {
|
|
2804
|
+
rotation,
|
|
2805
|
+
currentKeyPair,
|
|
2806
|
+
nextKeyPair,
|
|
2807
|
+
nextGeneration,
|
|
2808
|
+
coldRecoveryUsed: Boolean(coldRecoverySigningSecretKey)
|
|
2809
|
+
};
|
|
2810
|
+
}
|
|
2811
|
+
async function rotateIdentityPassword2({
|
|
2812
|
+
username,
|
|
2813
|
+
currentPassword,
|
|
2814
|
+
newPassword,
|
|
2815
|
+
coldPassword,
|
|
2816
|
+
currentGeneration = 1,
|
|
2817
|
+
reason = "password-change",
|
|
2818
|
+
pepper = "",
|
|
2819
|
+
kdfIterations,
|
|
2820
|
+
timestamp
|
|
2821
|
+
} = {}) {
|
|
2822
|
+
const currentKeyPair = await deriveKeyPairFromCredentials2({
|
|
2823
|
+
username,
|
|
2824
|
+
password: currentPassword,
|
|
2825
|
+
generation: currentGeneration,
|
|
2826
|
+
pepper,
|
|
2827
|
+
kdfIterations
|
|
2828
|
+
});
|
|
2829
|
+
const nextGeneration = currentGeneration + 1;
|
|
2830
|
+
const nextKeyPair = await deriveKeyPairFromCredentials2({
|
|
2831
|
+
username,
|
|
2832
|
+
password: newPassword,
|
|
2833
|
+
generation: nextGeneration,
|
|
2834
|
+
pepper,
|
|
2835
|
+
kdfIterations
|
|
2836
|
+
});
|
|
2837
|
+
const coldRecoverySigningSecretKey = await resolveColdRecoverySigningSecretKey({
|
|
2838
|
+
username,
|
|
2839
|
+
coldPassword,
|
|
2840
|
+
pepper,
|
|
2841
|
+
kdfIterations
|
|
2842
|
+
});
|
|
2843
|
+
const rotation = createIdentityRotation2({
|
|
2844
|
+
username,
|
|
2845
|
+
fromGeneration: currentGeneration,
|
|
2846
|
+
toGeneration: nextGeneration,
|
|
2847
|
+
previousPublicKey: keyPairToPublicBundle2(currentKeyPair),
|
|
2848
|
+
nextKeyPair,
|
|
2849
|
+
rotationKind: "password-change",
|
|
2850
|
+
reason,
|
|
2851
|
+
timestamp,
|
|
2852
|
+
coldRecoverySigningSecretKey
|
|
2853
|
+
});
|
|
2854
|
+
return {
|
|
2855
|
+
rotation,
|
|
2856
|
+
currentKeyPair,
|
|
2857
|
+
nextKeyPair,
|
|
2858
|
+
nextGeneration,
|
|
2859
|
+
coldRecoveryUsed: Boolean(coldRecoverySigningSecretKey)
|
|
2860
|
+
};
|
|
2861
|
+
}
|
|
2862
|
+
async function enrollColdRecoveryPassword2({
|
|
2863
|
+
username,
|
|
2864
|
+
coldPassword,
|
|
2865
|
+
pepper = "",
|
|
2866
|
+
kdfIterations,
|
|
2867
|
+
timestamp
|
|
2868
|
+
} = {}) {
|
|
2869
|
+
const coldRecovery = await deriveColdRecoverySigningKey2({
|
|
2870
|
+
username,
|
|
2871
|
+
coldPassword,
|
|
2872
|
+
pepper,
|
|
2873
|
+
kdfIterations
|
|
2874
|
+
});
|
|
2875
|
+
const enrollment = createColdRecoveryEnrollment({
|
|
2876
|
+
username,
|
|
2877
|
+
coldRecoverySigningSecretKey: coldRecovery.signing.secretKey,
|
|
2878
|
+
recoveryPublicKey: coldRecovery.recoveryPublicKey,
|
|
2879
|
+
timestamp
|
|
2880
|
+
});
|
|
2881
|
+
return {
|
|
2882
|
+
enrollment,
|
|
2883
|
+
recoveryPublicKey: coldRecovery.recoveryPublicKey,
|
|
2884
|
+
coldRecovery
|
|
2885
|
+
};
|
|
2886
|
+
}
|
|
2887
|
+
function shouldApplyIdentityRotation2(currentState, rotation, options = {}) {
|
|
2888
|
+
const requiredRecoveryPublicKey = options.enrolledRecoveryPublicKey || currentState && currentState.recoveryPublicKey || null;
|
|
2889
|
+
const verified = verifyIdentityRotation2(rotation, { requiredRecoveryPublicKey });
|
|
2890
|
+
if (!verified.ok) {
|
|
2891
|
+
return { apply: false, reason: verified.error };
|
|
2892
|
+
}
|
|
2893
|
+
if (currentState && rotation.toGeneration <= currentState.generation) {
|
|
2894
|
+
return { apply: false, reason: "stale-generation" };
|
|
2895
|
+
}
|
|
2896
|
+
if (currentState && currentState.publicKey && currentState.publicKey.signingPublicKey !== rotation.previousPublicKey.signingPublicKey) {
|
|
2897
|
+
return { apply: false, reason: "previous-key-mismatch" };
|
|
2898
|
+
}
|
|
2899
|
+
return { apply: true, rotation: verified.rotation };
|
|
2900
|
+
}
|
|
2901
|
+
module2.exports = {
|
|
2902
|
+
createIdentityRotation: createIdentityRotation2,
|
|
2903
|
+
createColdRecoveryEnrollment,
|
|
2904
|
+
verifyIdentityRotation: verifyIdentityRotation2,
|
|
2905
|
+
verifyColdRecoveryEnrollment: verifyColdRecoveryEnrollment2,
|
|
2906
|
+
revokeAndRotateIdentity: revokeAndRotateIdentity2,
|
|
2907
|
+
rotateIdentityPassword: rotateIdentityPassword2,
|
|
2908
|
+
enrollColdRecoveryPassword: enrollColdRecoveryPassword2,
|
|
2909
|
+
shouldApplyIdentityRotation: shouldApplyIdentityRotation2,
|
|
2910
|
+
keyPairToPublicBundle: keyPairToPublicBundle2,
|
|
2911
|
+
buildIdentityRotationPayload,
|
|
2912
|
+
buildColdRecoveryEnrollmentPayload,
|
|
2913
|
+
signIdentityRotationPayload
|
|
2914
|
+
};
|
|
2915
|
+
}
|
|
2916
|
+
});
|
|
2917
|
+
|
|
2427
2918
|
// src/security/message-security-service.js
|
|
2428
2919
|
var require_message_security_service = __commonJS({
|
|
2429
2920
|
"src/security/message-security-service.js"(exports2, module2) {
|
|
2430
2921
|
var nacl = require_nacl_fast();
|
|
2431
2922
|
var naclUtil = require_nacl_util();
|
|
2432
2923
|
var VDF2 = require_vdf();
|
|
2924
|
+
var DEFAULT_APP_PASSWORD2 = "change-this-app-password";
|
|
2433
2925
|
var DEFAULT_SECURITY_OPTIONS2 = {
|
|
2434
2926
|
enabled: true,
|
|
2435
2927
|
signingEnabled: true,
|
|
2436
2928
|
encryptionEnabled: true,
|
|
2437
2929
|
powEnabled: true,
|
|
2438
2930
|
powTargetMs: 1e3,
|
|
2439
|
-
appPassword:
|
|
2931
|
+
appPassword: DEFAULT_APP_PASSWORD2,
|
|
2440
2932
|
broadcastPasswords: {},
|
|
2441
2933
|
resolveBroadcastPassword: null,
|
|
2442
2934
|
powSteps: 22,
|
|
@@ -2531,16 +3023,85 @@ var require_message_security_service = __commonJS({
|
|
|
2531
3023
|
encryptionPublicKey: naclUtil.encodeBase64(this.encryptionPublicKey)
|
|
2532
3024
|
};
|
|
2533
3025
|
this.peerPublicKeys = /* @__PURE__ */ new Map();
|
|
3026
|
+
this.peerIdentityGenerations = /* @__PURE__ */ new Map();
|
|
3027
|
+
this.peerRecoveryPublicKeys = /* @__PURE__ */ new Map();
|
|
2534
3028
|
for (const [peerId, peerKey] of Object.entries(this.options.trustedPeerKeys || {})) {
|
|
2535
3029
|
this.peerPublicKeys.set(peerId, normalizePeerPublicKey(peerKey));
|
|
3030
|
+
this.peerIdentityGenerations.set(peerId, 1);
|
|
2536
3031
|
}
|
|
2537
3032
|
this.calibratedPowSteps = this.options.powSteps;
|
|
2538
3033
|
}
|
|
2539
3034
|
getPublicKey() {
|
|
2540
3035
|
return { ...this.publicKeyBundle };
|
|
2541
3036
|
}
|
|
2542
|
-
registerPeerPublicKey(peerId, publicKey) {
|
|
2543
|
-
|
|
3037
|
+
registerPeerPublicKey(peerId, publicKey, options = {}) {
|
|
3038
|
+
const normalized = normalizePeerPublicKey(publicKey);
|
|
3039
|
+
const generation = typeof options.generation === "number" ? options.generation : 1;
|
|
3040
|
+
const currentGeneration = this.peerIdentityGenerations.get(peerId) || 0;
|
|
3041
|
+
if (generation < currentGeneration && options.allowDowngrade !== true) {
|
|
3042
|
+
throw new Error(`Refusing older identity generation for peer ${peerId}`);
|
|
3043
|
+
}
|
|
3044
|
+
this.peerPublicKeys.set(peerId, normalized);
|
|
3045
|
+
this.peerIdentityGenerations.set(peerId, Math.max(currentGeneration, generation));
|
|
3046
|
+
}
|
|
3047
|
+
getPeerIdentityGeneration(peerId) {
|
|
3048
|
+
return this.peerIdentityGenerations.get(peerId) || 0;
|
|
3049
|
+
}
|
|
3050
|
+
getPeerIdentityState(peerId) {
|
|
3051
|
+
const publicKey = this.peerPublicKeys.get(peerId);
|
|
3052
|
+
const recoveryPublicKey = this.peerRecoveryPublicKeys.get(peerId) || null;
|
|
3053
|
+
if (!publicKey && !recoveryPublicKey) {
|
|
3054
|
+
return null;
|
|
3055
|
+
}
|
|
3056
|
+
return {
|
|
3057
|
+
publicKey: publicKey ? { ...publicKey } : null,
|
|
3058
|
+
generation: this.getPeerIdentityGeneration(peerId),
|
|
3059
|
+
recoveryPublicKey
|
|
3060
|
+
};
|
|
3061
|
+
}
|
|
3062
|
+
registerPeerRecoveryPublicKey(peerId, recoveryPublicKey) {
|
|
3063
|
+
if (!peerId || !recoveryPublicKey) {
|
|
3064
|
+
throw new Error("registerPeerRecoveryPublicKey requires peerId and recoveryPublicKey");
|
|
3065
|
+
}
|
|
3066
|
+
this.peerRecoveryPublicKeys.set(peerId, recoveryPublicKey);
|
|
3067
|
+
}
|
|
3068
|
+
getPeerRecoveryPublicKey(peerId) {
|
|
3069
|
+
return this.peerRecoveryPublicKeys.get(peerId) || null;
|
|
3070
|
+
}
|
|
3071
|
+
applyColdRecoveryEnrollment(peerId, enrollment) {
|
|
3072
|
+
const { verifyColdRecoveryEnrollment: verifyColdRecoveryEnrollment2 } = require_identity_rotation();
|
|
3073
|
+
const verified = verifyColdRecoveryEnrollment2(enrollment);
|
|
3074
|
+
if (!verified.ok) {
|
|
3075
|
+
const error = new Error(`Invalid cold recovery enrollment: ${verified.error}`);
|
|
3076
|
+
error.code = "INVALID_COLD_RECOVERY_ENROLLMENT";
|
|
3077
|
+
throw error;
|
|
3078
|
+
}
|
|
3079
|
+
this.registerPeerRecoveryPublicKey(peerId, enrollment.recoveryPublicKey);
|
|
3080
|
+
return { applied: true, recoveryPublicKey: enrollment.recoveryPublicKey };
|
|
3081
|
+
}
|
|
3082
|
+
applyIdentityRotation(peerId, rotation) {
|
|
3083
|
+
const { shouldApplyIdentityRotation: shouldApplyIdentityRotation2 } = require_identity_rotation();
|
|
3084
|
+
const currentState = this.getPeerIdentityState(peerId);
|
|
3085
|
+
const decision = shouldApplyIdentityRotation2(currentState, rotation, {
|
|
3086
|
+
enrolledRecoveryPublicKey: this.getPeerRecoveryPublicKey(peerId)
|
|
3087
|
+
});
|
|
3088
|
+
if (!decision.apply) {
|
|
3089
|
+
if (decision.reason && decision.reason !== "stale-generation" && decision.reason !== "previous-key-mismatch") {
|
|
3090
|
+
const error = new Error(`Invalid identity rotation: ${decision.reason}`);
|
|
3091
|
+
error.code = "INVALID_IDENTITY_ROTATION";
|
|
3092
|
+
throw error;
|
|
3093
|
+
}
|
|
3094
|
+
return { applied: false, reason: decision.reason };
|
|
3095
|
+
}
|
|
3096
|
+
this.registerPeerPublicKey(peerId, rotation.nextPublicKey, {
|
|
3097
|
+
generation: rotation.toGeneration
|
|
3098
|
+
});
|
|
3099
|
+
return {
|
|
3100
|
+
applied: true,
|
|
3101
|
+
fromGeneration: rotation.fromGeneration,
|
|
3102
|
+
toGeneration: rotation.toGeneration,
|
|
3103
|
+
rotationKind: rotation.rotationKind
|
|
3104
|
+
};
|
|
2544
3105
|
}
|
|
2545
3106
|
resolvePeerPublicKey(peerId, fallbackPublicKey) {
|
|
2546
3107
|
const trusted = this.peerPublicKeys.get(peerId);
|
|
@@ -2855,7 +3416,8 @@ var require_message_security_service = __commonJS({
|
|
|
2855
3416
|
stableStringify,
|
|
2856
3417
|
deriveBroadcastKey,
|
|
2857
3418
|
legacyBroadcastKey,
|
|
2858
|
-
DEFAULT_SECURITY_OPTIONS: DEFAULT_SECURITY_OPTIONS2
|
|
3419
|
+
DEFAULT_SECURITY_OPTIONS: DEFAULT_SECURITY_OPTIONS2,
|
|
3420
|
+
DEFAULT_APP_PASSWORD: DEFAULT_APP_PASSWORD2
|
|
2859
3421
|
};
|
|
2860
3422
|
}
|
|
2861
3423
|
});
|
|
@@ -2925,7 +3487,17 @@ var require_dignity_p2p = __commonJS({
|
|
|
2925
3487
|
var nacl = require_nacl_fast();
|
|
2926
3488
|
var naclUtil = require_nacl_util();
|
|
2927
3489
|
var EventEmitter = require_event_emitter();
|
|
2928
|
-
var {
|
|
3490
|
+
var {
|
|
3491
|
+
MessageSecurityService: MessageSecurityService2,
|
|
3492
|
+
stableStringify,
|
|
3493
|
+
DEFAULT_APP_PASSWORD: DEFAULT_APP_PASSWORD2
|
|
3494
|
+
} = require_message_security_service();
|
|
3495
|
+
var {
|
|
3496
|
+
revokeAndRotateIdentity: revokeAndRotateIdentity2,
|
|
3497
|
+
rotateIdentityPassword: rotateIdentityPassword2,
|
|
3498
|
+
enrollColdRecoveryPassword: enrollColdRecoveryPassword2
|
|
3499
|
+
} = require_identity_rotation();
|
|
3500
|
+
var { deriveKeyPairFromCredentials: deriveKeyPairFromCredentials2 } = require_derive_key_pair();
|
|
2929
3501
|
var {
|
|
2930
3502
|
DEFAULT_PEER_GROUP_OPTIONS: DEFAULT_PEER_GROUP_OPTIONS2,
|
|
2931
3503
|
peerGroupScope: peerGroupScope2,
|
|
@@ -2970,6 +3542,9 @@ var require_dignity_p2p = __commonJS({
|
|
|
2970
3542
|
this.defaultGossipMaxHops = security && typeof security.gossipMaxHops === "number" ? security.gossipMaxHops : DEFAULT_PEER_GROUP_OPTIONS2.maxHops;
|
|
2971
3543
|
this.globalMaxOpenConnections = security && typeof security.globalMaxOpenConnections === "number" ? security.globalMaxOpenConnections : 32;
|
|
2972
3544
|
this.gossipIdTtlMs = security && typeof security.gossipIdTtlMs === "number" ? security.gossipIdTtlMs : 5 * 60 * 1e3;
|
|
3545
|
+
this.maxSeenGossipIds = security && typeof security.maxSeenGossipIds === "number" ? security.maxSeenGossipIds : 1e5;
|
|
3546
|
+
this.gossipPublishMinIntervalMs = security && typeof security.gossipPublishMinIntervalMs === "number" ? security.gossipPublishMinIntervalMs : 0;
|
|
3547
|
+
this.lastGossipPublishAt = /* @__PURE__ */ new Map();
|
|
2973
3548
|
this.maxAppliedOperations = security && typeof security.maxAppliedOperations === "number" ? security.maxAppliedOperations : 5e4;
|
|
2974
3549
|
this.state = /* @__PURE__ */ new Map();
|
|
2975
3550
|
this.appliedOperations = /* @__PURE__ */ new Map();
|
|
@@ -2978,6 +3553,13 @@ var require_dignity_p2p = __commonJS({
|
|
|
2978
3553
|
async start() {
|
|
2979
3554
|
this.networkAdapter.onMessage(this.boundMessageHandler);
|
|
2980
3555
|
await this.networkAdapter.start(this.nodeId);
|
|
3556
|
+
const appPassword = this.securityService.options.appPassword;
|
|
3557
|
+
if (!appPassword || appPassword === DEFAULT_APP_PASSWORD2) {
|
|
3558
|
+
this.emit("warning", {
|
|
3559
|
+
type: "default-app-password",
|
|
3560
|
+
message: "Using the default appPassword is insecure; set a strong shared secret in production."
|
|
3561
|
+
});
|
|
3562
|
+
}
|
|
2981
3563
|
}
|
|
2982
3564
|
async stop() {
|
|
2983
3565
|
const joinedGroups = Array.from(this.peerGroups.keys());
|
|
@@ -3273,8 +3855,138 @@ var require_dignity_p2p = __commonJS({
|
|
|
3273
3855
|
connectToPeers: this.resolveReplicationPeers(collectionName, id, options, { fromRecord: existing })
|
|
3274
3856
|
});
|
|
3275
3857
|
}
|
|
3276
|
-
registerPeerPublicKey(peerId, publicKey) {
|
|
3277
|
-
this.securityService.registerPeerPublicKey(peerId, publicKey);
|
|
3858
|
+
registerPeerPublicKey(peerId, publicKey, options = {}) {
|
|
3859
|
+
this.securityService.registerPeerPublicKey(peerId, publicKey, options);
|
|
3860
|
+
}
|
|
3861
|
+
getPeerIdentityGeneration(peerId) {
|
|
3862
|
+
return this.securityService.getPeerIdentityGeneration(peerId);
|
|
3863
|
+
}
|
|
3864
|
+
getPeerIdentityState(peerId) {
|
|
3865
|
+
return this.securityService.getPeerIdentityState(peerId);
|
|
3866
|
+
}
|
|
3867
|
+
applyPeerIdentityRotation(peerId, rotation) {
|
|
3868
|
+
const result = this.securityService.applyIdentityRotation(peerId, rotation);
|
|
3869
|
+
if (result.applied) {
|
|
3870
|
+
this.emit("identityrotated", {
|
|
3871
|
+
peerId,
|
|
3872
|
+
username: rotation.username,
|
|
3873
|
+
fromGeneration: result.fromGeneration,
|
|
3874
|
+
toGeneration: result.toGeneration,
|
|
3875
|
+
rotationKind: result.rotationKind
|
|
3876
|
+
});
|
|
3877
|
+
}
|
|
3878
|
+
return result;
|
|
3879
|
+
}
|
|
3880
|
+
async broadcastIdentityRotation(rotation, options = {}) {
|
|
3881
|
+
return this.broadcastMessage("identity:rotate", rotation, options);
|
|
3882
|
+
}
|
|
3883
|
+
async broadcastColdRecoveryEnrollment(enrollment, options = {}) {
|
|
3884
|
+
return this.broadcastMessage("identity:cold-enroll", enrollment, options);
|
|
3885
|
+
}
|
|
3886
|
+
applyPeerColdRecoveryEnrollment(peerId, enrollment) {
|
|
3887
|
+
const result = this.securityService.applyColdRecoveryEnrollment(peerId, enrollment);
|
|
3888
|
+
if (result.applied) {
|
|
3889
|
+
this.emit("coldrecoveryenrolled", {
|
|
3890
|
+
peerId,
|
|
3891
|
+
username: enrollment.username,
|
|
3892
|
+
recoveryPublicKey: enrollment.recoveryPublicKey
|
|
3893
|
+
});
|
|
3894
|
+
}
|
|
3895
|
+
return result;
|
|
3896
|
+
}
|
|
3897
|
+
async enrollAndBroadcastColdRecovery({
|
|
3898
|
+
username,
|
|
3899
|
+
coldPassword,
|
|
3900
|
+
pepper = "",
|
|
3901
|
+
kdfIterations,
|
|
3902
|
+
broadcastOptions = {}
|
|
3903
|
+
} = {}) {
|
|
3904
|
+
const result = await enrollColdRecoveryPassword2({
|
|
3905
|
+
username,
|
|
3906
|
+
coldPassword,
|
|
3907
|
+
pepper,
|
|
3908
|
+
kdfIterations
|
|
3909
|
+
});
|
|
3910
|
+
await this.broadcastColdRecoveryEnrollment(result.enrollment, broadcastOptions);
|
|
3911
|
+
return result;
|
|
3912
|
+
}
|
|
3913
|
+
async revokeAndRotateDerivedIdentity({
|
|
3914
|
+
username,
|
|
3915
|
+
password,
|
|
3916
|
+
coldPassword,
|
|
3917
|
+
currentGeneration = 1,
|
|
3918
|
+
reason = "compromise-recovery",
|
|
3919
|
+
pepper = "",
|
|
3920
|
+
kdfIterations,
|
|
3921
|
+
broadcast = false,
|
|
3922
|
+
broadcastOptions = {}
|
|
3923
|
+
} = {}) {
|
|
3924
|
+
const result = await revokeAndRotateIdentity2({
|
|
3925
|
+
username,
|
|
3926
|
+
password,
|
|
3927
|
+
coldPassword,
|
|
3928
|
+
currentGeneration,
|
|
3929
|
+
reason,
|
|
3930
|
+
pepper,
|
|
3931
|
+
kdfIterations
|
|
3932
|
+
});
|
|
3933
|
+
if (broadcast) {
|
|
3934
|
+
await this.broadcastIdentityRotation(result.rotation, broadcastOptions);
|
|
3935
|
+
}
|
|
3936
|
+
return result;
|
|
3937
|
+
}
|
|
3938
|
+
async rotateDerivedIdentityPassword({
|
|
3939
|
+
username,
|
|
3940
|
+
currentPassword,
|
|
3941
|
+
newPassword,
|
|
3942
|
+
coldPassword,
|
|
3943
|
+
currentGeneration = 1,
|
|
3944
|
+
reason = "password-change",
|
|
3945
|
+
pepper = "",
|
|
3946
|
+
kdfIterations,
|
|
3947
|
+
broadcast = false,
|
|
3948
|
+
broadcastOptions = {}
|
|
3949
|
+
} = {}) {
|
|
3950
|
+
const result = await rotateIdentityPassword2({
|
|
3951
|
+
username,
|
|
3952
|
+
currentPassword,
|
|
3953
|
+
newPassword,
|
|
3954
|
+
coldPassword,
|
|
3955
|
+
currentGeneration,
|
|
3956
|
+
reason,
|
|
3957
|
+
pepper,
|
|
3958
|
+
kdfIterations
|
|
3959
|
+
});
|
|
3960
|
+
if (broadcast) {
|
|
3961
|
+
await this.broadcastIdentityRotation(result.rotation, broadcastOptions);
|
|
3962
|
+
}
|
|
3963
|
+
return result;
|
|
3964
|
+
}
|
|
3965
|
+
async adoptDerivedIdentityKeyPair(keyPair, { generation = 1 } = {}) {
|
|
3966
|
+
if (!keyPair || !keyPair.signing || !keyPair.encryption) {
|
|
3967
|
+
throw new Error("adoptDerivedIdentityKeyPair requires a derived keyPair");
|
|
3968
|
+
}
|
|
3969
|
+
this.securityService.signingSecretKey = keyPair.signing.secretKey;
|
|
3970
|
+
this.securityService.signingPublicKey = keyPair.signing.publicKey;
|
|
3971
|
+
this.securityService.encryptionSecretKey = keyPair.encryption.secretKey;
|
|
3972
|
+
this.securityService.encryptionPublicKey = keyPair.encryption.publicKey;
|
|
3973
|
+
this.securityService.publicKeyBundle = {
|
|
3974
|
+
signingPublicKey: naclUtil.encodeBase64(keyPair.signing.publicKey),
|
|
3975
|
+
encryptionPublicKey: naclUtil.encodeBase64(keyPair.encryption.publicKey)
|
|
3976
|
+
};
|
|
3977
|
+
this.securityService.options.keyPair = keyPair;
|
|
3978
|
+
this.securityService.options.identityGeneration = generation;
|
|
3979
|
+
}
|
|
3980
|
+
async deriveAndAdoptIdentity({ username, password, generation = 1, pepper = "", kdfIterations } = {}) {
|
|
3981
|
+
const keyPair = await deriveKeyPairFromCredentials2({
|
|
3982
|
+
username,
|
|
3983
|
+
password,
|
|
3984
|
+
generation,
|
|
3985
|
+
pepper,
|
|
3986
|
+
kdfIterations
|
|
3987
|
+
});
|
|
3988
|
+
await this.adoptDerivedIdentityKeyPair(keyPair, { generation });
|
|
3989
|
+
return keyPair;
|
|
3278
3990
|
}
|
|
3279
3991
|
trustPeerPublicKey(peerId, publicKey) {
|
|
3280
3992
|
if (!peerId || !publicKey) {
|
|
@@ -3396,6 +4108,13 @@ var require_dignity_p2p = __commonJS({
|
|
|
3396
4108
|
this.seenGossipIds.delete(gossipId);
|
|
3397
4109
|
}
|
|
3398
4110
|
}
|
|
4111
|
+
while (this.seenGossipIds.size > this.maxSeenGossipIds) {
|
|
4112
|
+
const oldestGossipId = this.seenGossipIds.keys().next().value;
|
|
4113
|
+
if (!oldestGossipId) {
|
|
4114
|
+
break;
|
|
4115
|
+
}
|
|
4116
|
+
this.seenGossipIds.delete(oldestGossipId);
|
|
4117
|
+
}
|
|
3399
4118
|
}
|
|
3400
4119
|
hasSeenGossip(gossipId) {
|
|
3401
4120
|
if (!gossipId) {
|
|
@@ -3409,6 +4128,7 @@ var require_dignity_p2p = __commonJS({
|
|
|
3409
4128
|
return;
|
|
3410
4129
|
}
|
|
3411
4130
|
this.seenGossipIds.set(gossipId, this.now() + this.gossipIdTtlMs);
|
|
4131
|
+
this.pruneSeenGossip();
|
|
3412
4132
|
}
|
|
3413
4133
|
listConnectedPeerIds() {
|
|
3414
4134
|
if (typeof this.networkAdapter.listOpenPeerIds === "function") {
|
|
@@ -3486,6 +4206,15 @@ var require_dignity_p2p = __commonJS({
|
|
|
3486
4206
|
if (!group && options.allowUnjoined !== true) {
|
|
3487
4207
|
throw new Error(`PeerGroup ${groupId} has not been joined`);
|
|
3488
4208
|
}
|
|
4209
|
+
if (this.gossipPublishMinIntervalMs > 0) {
|
|
4210
|
+
const lastPublishAt = this.lastGossipPublishAt.get(groupId) || 0;
|
|
4211
|
+
const elapsed = this.now() - lastPublishAt;
|
|
4212
|
+
if (elapsed < this.gossipPublishMinIntervalMs) {
|
|
4213
|
+
const error = new Error(`Gossip publish rate limit exceeded for group ${groupId}`);
|
|
4214
|
+
error.code = "GOSSIP_RATE_LIMIT";
|
|
4215
|
+
throw error;
|
|
4216
|
+
}
|
|
4217
|
+
}
|
|
3489
4218
|
const fanout = typeof options.fanout === "number" ? options.fanout : group ? group.fanout : this.defaultPeerGroupFanout;
|
|
3490
4219
|
const maxActivePeers = group ? group.maxActivePeers : this.defaultPeerGroupMaxActivePeers;
|
|
3491
4220
|
const maxHop = typeof options.maxHops === "number" ? options.maxHops : group ? group.maxHops : this.defaultGossipMaxHops;
|
|
@@ -3496,6 +4225,7 @@ var require_dignity_p2p = __commonJS({
|
|
|
3496
4225
|
}
|
|
3497
4226
|
const gossipId = options.gossipId || this.idGenerator();
|
|
3498
4227
|
this.markSeenGossip(gossipId);
|
|
4228
|
+
this.lastGossipPublishAt.set(groupId, this.now());
|
|
3499
4229
|
await this.broadcastMessage("peer-group:gossip", {
|
|
3500
4230
|
groupId,
|
|
3501
4231
|
gossipId,
|
|
@@ -3802,6 +4532,35 @@ var require_dignity_p2p = __commonJS({
|
|
|
3802
4532
|
if (!decrypted || decrypted.ignored) {
|
|
3803
4533
|
return;
|
|
3804
4534
|
}
|
|
4535
|
+
if (decrypted.messageType === "identity:rotate") {
|
|
4536
|
+
const peerId = decrypted.senderId || decrypted.payload?.username;
|
|
4537
|
+
if (peerId && decrypted.payload) {
|
|
4538
|
+
const result = this.applyPeerIdentityRotation(peerId, decrypted.payload);
|
|
4539
|
+
if (!result.applied) {
|
|
4540
|
+
this.emit("warning", {
|
|
4541
|
+
type: "identity-rotation-ignored",
|
|
4542
|
+
peerId,
|
|
4543
|
+
reason: result.reason
|
|
4544
|
+
});
|
|
4545
|
+
}
|
|
4546
|
+
}
|
|
4547
|
+
return;
|
|
4548
|
+
}
|
|
4549
|
+
if (decrypted.messageType === "identity:cold-enroll") {
|
|
4550
|
+
const peerId = decrypted.senderId || decrypted.payload?.username;
|
|
4551
|
+
if (peerId && decrypted.payload) {
|
|
4552
|
+
try {
|
|
4553
|
+
this.applyPeerColdRecoveryEnrollment(peerId, decrypted.payload);
|
|
4554
|
+
} catch (error) {
|
|
4555
|
+
this.emit("warning", {
|
|
4556
|
+
type: "cold-recovery-enrollment-rejected",
|
|
4557
|
+
peerId,
|
|
4558
|
+
error
|
|
4559
|
+
});
|
|
4560
|
+
}
|
|
4561
|
+
}
|
|
4562
|
+
return;
|
|
4563
|
+
}
|
|
3805
4564
|
if (message && message.senderId && message.senderPublicKey) {
|
|
3806
4565
|
this.trustPeerPublicKey(message.senderId, message.senderPublicKey);
|
|
3807
4566
|
}
|
|
@@ -3813,7 +4572,10 @@ var require_dignity_p2p = __commonJS({
|
|
|
3813
4572
|
const payload = decrypted.payload || {};
|
|
3814
4573
|
const { collectionName, record } = payload;
|
|
3815
4574
|
if (collectionName && record) {
|
|
3816
|
-
const applied = this.restoreRecord(collectionName, record
|
|
4575
|
+
const applied = this.restoreRecord(collectionName, record, {
|
|
4576
|
+
rejectOnHashMismatch: true,
|
|
4577
|
+
via: "direct-mesh"
|
|
4578
|
+
});
|
|
3817
4579
|
if (applied) {
|
|
3818
4580
|
this.emit("change", {
|
|
3819
4581
|
kind: "snapshot",
|
|
@@ -4318,7 +5080,7 @@ var require_websocket_signaling_provider = __commonJS({
|
|
|
4318
5080
|
// src/signaling/parse-peerjs-url.js
|
|
4319
5081
|
var require_parse_peerjs_url = __commonJS({
|
|
4320
5082
|
"src/signaling/parse-peerjs-url.js"(exports2, module2) {
|
|
4321
|
-
function
|
|
5083
|
+
function parsePeerJsServerUrl2(url) {
|
|
4322
5084
|
const parsed = new URL(url);
|
|
4323
5085
|
const secure = parsed.protocol === "wss:";
|
|
4324
5086
|
const host = parsed.hostname;
|
|
@@ -4333,7 +5095,7 @@ var require_parse_peerjs_url = __commonJS({
|
|
|
4333
5095
|
}
|
|
4334
5096
|
return { secure, host, port, path, key };
|
|
4335
5097
|
}
|
|
4336
|
-
module2.exports =
|
|
5098
|
+
module2.exports = parsePeerJsServerUrl2;
|
|
4337
5099
|
}
|
|
4338
5100
|
});
|
|
4339
5101
|
|
|
@@ -10926,7 +11688,7 @@ var require_bundler = __commonJS({
|
|
|
10926
11688
|
var require_peerjs_signaling_provider = __commonJS({
|
|
10927
11689
|
"src/signaling/peerjs-signaling-provider.js"(exports2, module2) {
|
|
10928
11690
|
var WebSocketSignalingProvider2 = require_websocket_signaling_provider();
|
|
10929
|
-
var
|
|
11691
|
+
var parsePeerJsServerUrl2 = require_parse_peerjs_url();
|
|
10930
11692
|
var PeerJSSignalingProvider2 = class {
|
|
10931
11693
|
constructor({ id, url, PeerImpl, WebSocketImpl, priority = 0, connectTimeoutMs = 1e4 }) {
|
|
10932
11694
|
if (!url) {
|
|
@@ -10954,7 +11716,7 @@ var require_peerjs_signaling_provider = __commonJS({
|
|
|
10954
11716
|
}
|
|
10955
11717
|
}
|
|
10956
11718
|
parsePeerJsServerUrl() {
|
|
10957
|
-
return
|
|
11719
|
+
return parsePeerJsServerUrl2(this.url);
|
|
10958
11720
|
}
|
|
10959
11721
|
shouldUseWebSocketFallback() {
|
|
10960
11722
|
return !this.isCustomPeerImpl && typeof globalThis.RTCPeerConnection !== "function";
|
|
@@ -11271,7 +12033,7 @@ var require_in_memory_network = __commonJS({
|
|
|
11271
12033
|
var require_peerjs_network = __commonJS({
|
|
11272
12034
|
"src/network/peerjs-network.js"(exports2, module2) {
|
|
11273
12035
|
var { DEFAULT_CLOUDFLARE_SIGNALING_URLS: DEFAULT_CLOUDFLARE_SIGNALING_URLS2 } = require_default_signaling_config();
|
|
11274
|
-
var
|
|
12036
|
+
var parsePeerJsServerUrl2 = require_parse_peerjs_url();
|
|
11275
12037
|
function resolvePeerImplementation(PeerImpl) {
|
|
11276
12038
|
if (PeerImpl) {
|
|
11277
12039
|
return PeerImpl;
|
|
@@ -11324,7 +12086,7 @@ var require_peerjs_network = __commonJS({
|
|
|
11324
12086
|
}
|
|
11325
12087
|
async startWithUrl(nodeId, url) {
|
|
11326
12088
|
this.nodeId = nodeId;
|
|
11327
|
-
const server =
|
|
12089
|
+
const server = parsePeerJsServerUrl2(url);
|
|
11328
12090
|
await new Promise((resolve, reject) => {
|
|
11329
12091
|
const peer = new this.PeerImpl(nodeId, {
|
|
11330
12092
|
host: server.host,
|
|
@@ -11484,7 +12246,7 @@ var require_peerjs_network = __commonJS({
|
|
|
11484
12246
|
module2.exports = {
|
|
11485
12247
|
PeerJSNetworkAdapter: PeerJSNetworkAdapter2,
|
|
11486
12248
|
createPeerJSNetworkAdapter: createPeerJSNetworkAdapter2,
|
|
11487
|
-
parsePeerJsServerUrl
|
|
12249
|
+
parsePeerJsServerUrl: parsePeerJsServerUrl2
|
|
11488
12250
|
};
|
|
11489
12251
|
}
|
|
11490
12252
|
});
|
|
@@ -11673,8 +12435,20 @@ var VDF = require_vdf();
|
|
|
11673
12435
|
var SlothPermutation = require_sloth_vdf();
|
|
11674
12436
|
var {
|
|
11675
12437
|
MessageSecurityService,
|
|
11676
|
-
DEFAULT_SECURITY_OPTIONS
|
|
12438
|
+
DEFAULT_SECURITY_OPTIONS,
|
|
12439
|
+
DEFAULT_APP_PASSWORD
|
|
11677
12440
|
} = require_message_security_service();
|
|
12441
|
+
var { deriveKeyPairFromCredentials, keyPairToPublicBundle, deriveColdRecoverySigningKey } = require_derive_key_pair();
|
|
12442
|
+
var {
|
|
12443
|
+
createIdentityRotation,
|
|
12444
|
+
verifyIdentityRotation,
|
|
12445
|
+
revokeAndRotateIdentity,
|
|
12446
|
+
rotateIdentityPassword,
|
|
12447
|
+
enrollColdRecoveryPassword,
|
|
12448
|
+
verifyColdRecoveryEnrollment,
|
|
12449
|
+
shouldApplyIdentityRotation
|
|
12450
|
+
} = require_identity_rotation();
|
|
12451
|
+
var parsePeerJsServerUrl = require_parse_peerjs_url();
|
|
11678
12452
|
var {
|
|
11679
12453
|
PEER_GROUP_SCOPE_PREFIX,
|
|
11680
12454
|
DEFAULT_PEER_GROUP_OPTIONS,
|
|
@@ -11699,6 +12473,18 @@ module.exports = {
|
|
|
11699
12473
|
SlothPermutation,
|
|
11700
12474
|
MessageSecurityService,
|
|
11701
12475
|
DEFAULT_SECURITY_OPTIONS,
|
|
12476
|
+
DEFAULT_APP_PASSWORD,
|
|
12477
|
+
deriveKeyPairFromCredentials,
|
|
12478
|
+
deriveColdRecoverySigningKey,
|
|
12479
|
+
keyPairToPublicBundle,
|
|
12480
|
+
createIdentityRotation,
|
|
12481
|
+
verifyIdentityRotation,
|
|
12482
|
+
revokeAndRotateIdentity,
|
|
12483
|
+
rotateIdentityPassword,
|
|
12484
|
+
enrollColdRecoveryPassword,
|
|
12485
|
+
verifyColdRecoveryEnrollment,
|
|
12486
|
+
shouldApplyIdentityRotation,
|
|
12487
|
+
parsePeerJsServerUrl,
|
|
11702
12488
|
PEER_GROUP_SCOPE_PREFIX,
|
|
11703
12489
|
DEFAULT_PEER_GROUP_OPTIONS,
|
|
11704
12490
|
peerGroupScope,
|