dignity.js 0.2.0 → 0.3.0
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 +22 -16
- package/dist/dignity.cjs.js +45 -6
- package/dist/dignity.cjs.js.map +2 -2
- package/dist/dignity.esm.js +45 -6
- package/dist/dignity.esm.js.map +2 -2
- package/dist/dignity.min.js +5 -5
- package/package.json +2 -2
- package/src/security/message-security-service.js +51 -6
package/README.md
CHANGED
|
@@ -13,10 +13,9 @@
|
|
|
13
13
|
<p align="center">
|
|
14
14
|
<a href="https://www.npmjs.com/package/dignity.js"><img src="https://img.shields.io/npm/v/dignity.js?color=cb3837&label=npm" alt="npm version"></a>
|
|
15
15
|
<a href="https://www.npmjs.com/package/dignity.js"><img src="https://img.shields.io/npm/dm/dignity.js?color=blue" alt="npm downloads"></a>
|
|
16
|
-
<img src="https://img.shields.io/badge/tests-
|
|
17
|
-
<img src="https://img.shields.io/badge/coverage-
|
|
18
|
-
<img src="https://img.shields.io/badge/license-
|
|
19
|
-
<img src="https://img.shields.io/badge/minified-51KB-purple" alt="bundle size">
|
|
16
|
+
<img src="https://img.shields.io/badge/tests-122%20passing-brightgreen" alt="tests passing">
|
|
17
|
+
<img src="https://img.shields.io/badge/coverage-97%25-brightgreen" alt="coverage">
|
|
18
|
+
<img src="https://img.shields.io/badge/license-Apache%202.0-black" alt="license">
|
|
20
19
|
</p>
|
|
21
20
|
|
|
22
21
|
REST-like P2P object API for decentralized JavaScript applications.
|
|
@@ -35,7 +34,7 @@ REST-like P2P object API for decentralized JavaScript applications.
|
|
|
35
34
|
- default `powSteps: 22` (calibrated on this machine to about 1000ms)
|
|
36
35
|
- automatic peer ban on invalid signature/PoW (`48h` default)
|
|
37
36
|
- Team/subapp scoped broadcast passwords (`broadcastScope` + `broadcastPasswords`)
|
|
38
|
-
- Browser-first
|
|
37
|
+
- Browser-first: published npm package includes IIFE, ESM, and CJS builds
|
|
39
38
|
|
|
40
39
|
## Install
|
|
41
40
|
|
|
@@ -146,15 +145,9 @@ bob.registerPeerPublicKey('alice', alice.getPublicKey());
|
|
|
146
145
|
await alice.sendDirectMessage('bob', 'dm', { text: 'private payload' });
|
|
147
146
|
```
|
|
148
147
|
|
|
149
|
-
## Browser
|
|
148
|
+
## Browser Usage
|
|
150
149
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
- `dist/dignity.min.js` (IIFE, global `DignityJS`)
|
|
154
|
-
- `dist/dignity.esm.js` (ESM)
|
|
155
|
-
- `dist/dignity.cjs.js` (CommonJS)
|
|
156
|
-
|
|
157
|
-
Example with CDN:
|
|
150
|
+
The published npm package includes pre-built bundles (IIFE, ESM, CJS) generated at publish time. The `dist/` folder is not checked into the repository.
|
|
158
151
|
|
|
159
152
|
```html
|
|
160
153
|
<script src="https://unpkg.com/dignity.js/dist/dignity.min.js"></script>
|
|
@@ -163,6 +156,19 @@ Example with CDN:
|
|
|
163
156
|
</script>
|
|
164
157
|
```
|
|
165
158
|
|
|
159
|
+
## Security Model
|
|
160
|
+
|
|
161
|
+
`dignity.js` provides two encryption modes:
|
|
162
|
+
|
|
163
|
+
- **Direct mode** (`targetId` set): true end-to-end encryption using X25519 key exchange between sender and recipient. Only the intended recipient can decrypt.
|
|
164
|
+
- **Broadcast mode** (no `targetId`): symmetric encryption using a shared password. All peers that know the password can decrypt all broadcast traffic in that scope. This is a **group shared-secret cipher**, not end-to-end encryption.
|
|
165
|
+
|
|
166
|
+
Broadcast encryption uses PBKDF2-SHA256 (default 100,000 iterations) with a random salt per message to derive the symmetric key. This protects against offline brute-force of weak passwords. The iteration count is configurable via `kdfIterations`.
|
|
167
|
+
|
|
168
|
+
Messages from peers running older versions that used the legacy single-hash KDF are still accepted and decrypted automatically (backward compatible).
|
|
169
|
+
|
|
170
|
+
**Important:** if the broadcast password leaks, all past captured traffic for that scope is retroactively decryptable. For sensitive data, use direct mode with per-peer public keys.
|
|
171
|
+
|
|
166
172
|
## Signaling Servers
|
|
167
173
|
|
|
168
174
|
Default signaling URLs include PeerJS-compatible public endpoints:
|
|
@@ -198,11 +204,11 @@ npm run test:pow-calibrate
|
|
|
198
204
|
## Publish
|
|
199
205
|
|
|
200
206
|
```bash
|
|
201
|
-
npm test
|
|
202
|
-
npm run build
|
|
203
207
|
npm publish --access public
|
|
204
208
|
```
|
|
205
209
|
|
|
210
|
+
The `prepublishOnly` script runs tests and build automatically.
|
|
211
|
+
|
|
206
212
|
## License
|
|
207
213
|
|
|
208
|
-
Apache 2.0
|
|
214
|
+
Apache 2.0 — see [LICENSE](LICENSE).
|
package/dist/dignity.cjs.js
CHANGED
|
@@ -2434,7 +2434,8 @@ var require_message_security_service = __commonJS({
|
|
|
2434
2434
|
broadcastPasswords: {},
|
|
2435
2435
|
resolveBroadcastPassword: null,
|
|
2436
2436
|
powSteps: 22,
|
|
2437
|
-
trustedPeerKeys: {}
|
|
2437
|
+
trustedPeerKeys: {},
|
|
2438
|
+
kdfIterations: 1e5
|
|
2438
2439
|
};
|
|
2439
2440
|
function stableStringify(value) {
|
|
2440
2441
|
if (value === null || typeof value !== "object") {
|
|
@@ -2461,6 +2462,33 @@ var require_message_security_service = __commonJS({
|
|
|
2461
2462
|
function utf8ToBytes(value) {
|
|
2462
2463
|
return naclUtil.decodeUTF8(value);
|
|
2463
2464
|
}
|
|
2465
|
+
async function deriveBroadcastKey(password, salt, iterations) {
|
|
2466
|
+
const subtle = globalThis.crypto && globalThis.crypto.subtle;
|
|
2467
|
+
if (subtle) {
|
|
2468
|
+
const keyMaterial = await subtle.importKey(
|
|
2469
|
+
"raw",
|
|
2470
|
+
utf8ToBytes(password),
|
|
2471
|
+
"PBKDF2",
|
|
2472
|
+
false,
|
|
2473
|
+
["deriveBits"]
|
|
2474
|
+
);
|
|
2475
|
+
const bits = await subtle.deriveBits(
|
|
2476
|
+
{ name: "PBKDF2", salt, iterations, hash: "SHA-256" },
|
|
2477
|
+
keyMaterial,
|
|
2478
|
+
256
|
|
2479
|
+
);
|
|
2480
|
+
return new Uint8Array(bits);
|
|
2481
|
+
}
|
|
2482
|
+
try {
|
|
2483
|
+
const { pbkdf2Sync } = require("crypto");
|
|
2484
|
+
return new Uint8Array(pbkdf2Sync(password, Buffer.from(salt), iterations, 32, "sha256"));
|
|
2485
|
+
} catch (_ignored) {
|
|
2486
|
+
return hash32(concatBytes(utf8ToBytes(password), salt));
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
function legacyBroadcastKey(password, salt) {
|
|
2490
|
+
return hash32(concatBytes(utf8ToBytes(password), salt));
|
|
2491
|
+
}
|
|
2464
2492
|
function normalizePeerPublicKey(publicKey) {
|
|
2465
2493
|
if (!publicKey || typeof publicKey !== "object") {
|
|
2466
2494
|
throw new Error("Public key must be an object with signingPublicKey and encryptionPublicKey");
|
|
@@ -2621,7 +2649,7 @@ var require_message_security_service = __commonJS({
|
|
|
2621
2649
|
if (envelope.security && envelope.security.signing && envelope.security.signing.enabled && this.options.signingEnabled) {
|
|
2622
2650
|
this.verifySignature(envelope);
|
|
2623
2651
|
}
|
|
2624
|
-
const payload = this.decryptPayload(envelope);
|
|
2652
|
+
const payload = await this.decryptPayload(envelope);
|
|
2625
2653
|
return {
|
|
2626
2654
|
ignored: false,
|
|
2627
2655
|
messageType: envelope.messageType,
|
|
@@ -2686,7 +2714,8 @@ var require_message_security_service = __commonJS({
|
|
|
2686
2714
|
const nonce = nacl.randomBytes(nacl.secretbox.nonceLength);
|
|
2687
2715
|
const salt = nacl.randomBytes(16);
|
|
2688
2716
|
const password = this.resolveBroadcastPassword(scope);
|
|
2689
|
-
const
|
|
2717
|
+
const iterations = this.options.kdfIterations || DEFAULT_SECURITY_OPTIONS2.kdfIterations;
|
|
2718
|
+
const key = await deriveBroadcastKey(password, salt, iterations);
|
|
2690
2719
|
const encrypted = nacl.secretbox(plainText, nonce, key);
|
|
2691
2720
|
return {
|
|
2692
2721
|
payload: naclUtil.encodeBase64(encrypted),
|
|
@@ -2695,11 +2724,13 @@ var require_message_security_service = __commonJS({
|
|
|
2695
2724
|
mode: "broadcast",
|
|
2696
2725
|
scope,
|
|
2697
2726
|
nonce: naclUtil.encodeBase64(nonce),
|
|
2698
|
-
salt: naclUtil.encodeBase64(salt)
|
|
2727
|
+
salt: naclUtil.encodeBase64(salt),
|
|
2728
|
+
kdf: "pbkdf2",
|
|
2729
|
+
kdfIterations: iterations
|
|
2699
2730
|
}
|
|
2700
2731
|
};
|
|
2701
2732
|
}
|
|
2702
|
-
decryptPayload(envelope) {
|
|
2733
|
+
async decryptPayload(envelope) {
|
|
2703
2734
|
const encryption = envelope.security ? envelope.security.encryption : null;
|
|
2704
2735
|
if (!encryption || !encryption.enabled || !this.options.encryptionEnabled) {
|
|
2705
2736
|
return envelope.payload;
|
|
@@ -2710,7 +2741,13 @@ var require_message_security_service = __commonJS({
|
|
|
2710
2741
|
const password = this.resolveBroadcastPassword(scope);
|
|
2711
2742
|
const salt = naclUtil.decodeBase64(encryption.salt);
|
|
2712
2743
|
const nonce = naclUtil.decodeBase64(encryption.nonce);
|
|
2713
|
-
|
|
2744
|
+
let key;
|
|
2745
|
+
if (encryption.kdf === "pbkdf2") {
|
|
2746
|
+
const iterations = encryption.kdfIterations || DEFAULT_SECURITY_OPTIONS2.kdfIterations;
|
|
2747
|
+
key = await deriveBroadcastKey(password, salt, iterations);
|
|
2748
|
+
} else {
|
|
2749
|
+
key = legacyBroadcastKey(password, salt);
|
|
2750
|
+
}
|
|
2714
2751
|
const decrypted = nacl.secretbox.open(encryptedBuffer, nonce, key);
|
|
2715
2752
|
if (!decrypted) {
|
|
2716
2753
|
throw new Error("Unable to decrypt broadcast payload");
|
|
@@ -2804,6 +2841,8 @@ var require_message_security_service = __commonJS({
|
|
|
2804
2841
|
module2.exports = {
|
|
2805
2842
|
MessageSecurityService: MessageSecurityService2,
|
|
2806
2843
|
stableStringify,
|
|
2844
|
+
deriveBroadcastKey,
|
|
2845
|
+
legacyBroadcastKey,
|
|
2807
2846
|
DEFAULT_SECURITY_OPTIONS: DEFAULT_SECURITY_OPTIONS2
|
|
2808
2847
|
};
|
|
2809
2848
|
}
|