@freesignal/protocol 0.7.11 → 0.8.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/dist/double-ratchet.d.ts +1 -1
- package/dist/double-ratchet.js +28 -35
- package/dist/index.d.ts +4 -4
- package/dist/index.js +14 -34
- package/dist/node.d.ts +5 -4
- package/dist/node.js +60 -62
- package/dist/types.d.ts +1 -1
- package/dist/types.js +75 -91
- package/dist/x3dh.d.ts +2 -2
- package/dist/x3dh.js +49 -56
- package/package.json +45 -45
package/dist/double-ratchet.d.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* You should have received a copy of the GNU General Public License
|
|
17
17
|
* along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
18
18
|
*/
|
|
19
|
-
import { IdentityKey, UserId } from "./types";
|
|
19
|
+
import { IdentityKey, UserId } from "./types.js";
|
|
20
20
|
export interface ExportedKeySession {
|
|
21
21
|
identityKey: string;
|
|
22
22
|
secretKey: string;
|
package/dist/double-ratchet.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* FreeSignal Protocol
|
|
4
3
|
*
|
|
@@ -17,31 +16,26 @@
|
|
|
17
16
|
* You should have received a copy of the GNU General Public License
|
|
18
17
|
* along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
19
18
|
*/
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
};
|
|
23
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
-
exports.KeySession = void 0;
|
|
25
|
-
const crypto_1 = __importDefault(require("@freesignal/crypto"));
|
|
26
|
-
const utils_1 = require("@freesignal/utils");
|
|
27
|
-
const types_1 = require("./types");
|
|
19
|
+
import crypto from "@freesignal/crypto";
|
|
20
|
+
import { decodeBase64, encodeBase64, compareBytes } from "@freesignal/utils";
|
|
21
|
+
import { IdentityKey } from "./types.js";
|
|
28
22
|
/**
|
|
29
23
|
* Represents a secure Double Ratchet session.
|
|
30
24
|
* Used for forward-secure encryption and decryption of messages.
|
|
31
25
|
*/
|
|
32
|
-
class KeySession {
|
|
26
|
+
export class KeySession {
|
|
33
27
|
constructor({ identityKey, secretKey, remoteKey, rootKey, headerKey, nextHeaderKey }) {
|
|
34
28
|
this.headerKeys = new Map();
|
|
35
29
|
this.previousKeys = new KeyMap();
|
|
36
30
|
this.identityKey = identityKey;
|
|
37
31
|
this.rootKey = rootKey;
|
|
38
|
-
this.sessionTag =
|
|
39
|
-
this.keyPair =
|
|
32
|
+
this.sessionTag = decodeBase64(crypto.hkdf(rootKey, new Uint8Array(32).fill(0), "/freesignal/session-authtag", 32));
|
|
33
|
+
this.keyPair = crypto.ECDH.keyPair(secretKey);
|
|
40
34
|
if (headerKey)
|
|
41
35
|
this.headerKey = headerKey;
|
|
42
36
|
if (nextHeaderKey) {
|
|
43
37
|
this.nextHeaderKey = nextHeaderKey;
|
|
44
|
-
this.headerKeys.set(
|
|
38
|
+
this.headerKeys.set(decodeBase64(crypto.hash(nextHeaderKey)), nextHeaderKey);
|
|
45
39
|
}
|
|
46
40
|
if (remoteKey) {
|
|
47
41
|
this.sendingChain = this.getChain(remoteKey, this.headerKey);
|
|
@@ -52,10 +46,10 @@ class KeySession {
|
|
|
52
46
|
return this.identityKey.userId;
|
|
53
47
|
}
|
|
54
48
|
getChain(remoteKey, headerKey, previousCount) {
|
|
55
|
-
const sharedKey =
|
|
49
|
+
const sharedKey = crypto.ECDH.scalarMult(this.keyPair.secretKey, remoteKey);
|
|
56
50
|
if (!this.rootKey)
|
|
57
|
-
this.rootKey =
|
|
58
|
-
const hashkey =
|
|
51
|
+
this.rootKey = crypto.hash(sharedKey);
|
|
52
|
+
const hashkey = crypto.hkdf(sharedKey, this.rootKey, KeySession.info, KeySession.keyLength * 3);
|
|
59
53
|
this.rootKey = hashkey.subarray(0, KeySession.keyLength);
|
|
60
54
|
return new KeyChain(this.publicKey, remoteKey, hashkey.subarray(KeySession.keyLength, KeySession.keyLength * 2), hashkey.subarray(KeySession.keyLength * 2), headerKey, previousCount);
|
|
61
55
|
}
|
|
@@ -79,17 +73,17 @@ class KeySession {
|
|
|
79
73
|
}
|
|
80
74
|
getReceivingKey(encryptionKeys) {
|
|
81
75
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
82
|
-
if (!this.previousKeys.has(
|
|
83
|
-
if (!
|
|
76
|
+
if (!this.previousKeys.has(decodeBase64(encryptionKeys.publicKey) + encryptionKeys.count.toString())) {
|
|
77
|
+
if (!compareBytes(encryptionKeys.publicKey, (_b = (_a = this.receivingChain) === null || _a === void 0 ? void 0 : _a.remoteKey) !== null && _b !== void 0 ? _b : new Uint8Array())) {
|
|
84
78
|
while (this.receivingChain && this.receivingChain.count < encryptionKeys.previous) {
|
|
85
79
|
const key = this.receivingChain.getKey();
|
|
86
|
-
this.previousKeys.set(
|
|
80
|
+
this.previousKeys.set(decodeBase64(this.receivingChain.remoteKey) + this.receivingChain.count.toString(), key);
|
|
87
81
|
}
|
|
88
82
|
this.receivingChain = this.getChain(encryptionKeys.publicKey, (_c = this.nextHeaderKey) !== null && _c !== void 0 ? _c : (_d = this.receivingChain) === null || _d === void 0 ? void 0 : _d.nextHeaderKey, (_e = this.receivingChain) === null || _e === void 0 ? void 0 : _e.count);
|
|
89
|
-
this.headerKeys.set(
|
|
83
|
+
this.headerKeys.set(decodeBase64(crypto.hash(this.receivingChain.nextHeaderKey)), this.receivingChain.nextHeaderKey);
|
|
90
84
|
if (this.nextHeaderKey)
|
|
91
85
|
this.nextHeaderKey = undefined;
|
|
92
|
-
this.keyPair =
|
|
86
|
+
this.keyPair = crypto.ECDH.keyPair();
|
|
93
87
|
this.sendingChain = this.getChain(encryptionKeys.publicKey, (_f = this.headerKey) !== null && _f !== void 0 ? _f : (_g = this.sendingChain) === null || _g === void 0 ? void 0 : _g.nextHeaderKey, (_h = this.sendingChain) === null || _h === void 0 ? void 0 : _h.count);
|
|
94
88
|
if (this.headerKey)
|
|
95
89
|
this.headerKey = undefined;
|
|
@@ -98,10 +92,10 @@ class KeySession {
|
|
|
98
92
|
throw new Error("Error initializing receivingChain");
|
|
99
93
|
while (this.receivingChain.count < encryptionKeys.count) {
|
|
100
94
|
const key = this.receivingChain.getKey();
|
|
101
|
-
this.previousKeys.set(
|
|
95
|
+
this.previousKeys.set(decodeBase64(this.receivingChain.remoteKey) + this.receivingChain.count.toString(), key);
|
|
102
96
|
}
|
|
103
97
|
}
|
|
104
|
-
return this.previousKeys.get(
|
|
98
|
+
return this.previousKeys.get(decodeBase64(encryptionKeys.publicKey) + encryptionKeys.count.toString());
|
|
105
99
|
}
|
|
106
100
|
/**
|
|
107
101
|
* Whether both the sending and receiving chains are initialized.
|
|
@@ -118,11 +112,11 @@ class KeySession {
|
|
|
118
112
|
var _a, _b;
|
|
119
113
|
return {
|
|
120
114
|
identityKey: this.identityKey.toString(),
|
|
121
|
-
secretKey:
|
|
122
|
-
rootKey:
|
|
115
|
+
secretKey: decodeBase64(this.keyPair.secretKey),
|
|
116
|
+
rootKey: decodeBase64(this.rootKey),
|
|
123
117
|
sendingChain: (_a = this.sendingChain) === null || _a === void 0 ? void 0 : _a.toJSON(),
|
|
124
118
|
receivingChain: (_b = this.receivingChain) === null || _b === void 0 ? void 0 : _b.toJSON(),
|
|
125
|
-
headerKey: this.headerKey ?
|
|
119
|
+
headerKey: this.headerKey ? decodeBase64(this.headerKey) : undefined,
|
|
126
120
|
headerKeys: Array.from(this.headerKeys.entries()),
|
|
127
121
|
previousKeys: Array.from(this.previousKeys.entries())
|
|
128
122
|
};
|
|
@@ -134,14 +128,13 @@ class KeySession {
|
|
|
134
128
|
* @returns session with the state parsed.
|
|
135
129
|
*/
|
|
136
130
|
static from(data) {
|
|
137
|
-
const session = new KeySession({ identityKey:
|
|
131
|
+
const session = new KeySession({ identityKey: IdentityKey.from(data.identityKey), secretKey: encodeBase64(data.secretKey), rootKey: encodeBase64(data.rootKey) });
|
|
138
132
|
session.sendingChain = data.sendingChain ? KeyChain.from(data.sendingChain) : undefined;
|
|
139
133
|
session.receivingChain = data.receivingChain ? KeyChain.from(data.receivingChain) : undefined;
|
|
140
134
|
session.previousKeys = new KeyMap(data.previousKeys);
|
|
141
135
|
return session;
|
|
142
136
|
}
|
|
143
137
|
}
|
|
144
|
-
exports.KeySession = KeySession;
|
|
145
138
|
KeySession.keyLength = 32;
|
|
146
139
|
KeySession.version = 1;
|
|
147
140
|
KeySession.info = "/freesignal/double-ratchet/v0." + KeySession.version;
|
|
@@ -159,7 +152,7 @@ class KeyChain {
|
|
|
159
152
|
getKey() {
|
|
160
153
|
if (++this._count >= KeySession.maxCount)
|
|
161
154
|
throw new Error("SendingChain count too big");
|
|
162
|
-
const hash =
|
|
155
|
+
const hash = crypto.hkdf(this.chainKey, new Uint8Array(KeySession.keyLength).fill(0), KeySession.info, KeySession.keyLength * 2);
|
|
163
156
|
this.chainKey = hash.subarray(0, KeySession.keyLength);
|
|
164
157
|
return hash.subarray(KeySession.keyLength);
|
|
165
158
|
}
|
|
@@ -171,18 +164,18 @@ class KeyChain {
|
|
|
171
164
|
}
|
|
172
165
|
toJSON() {
|
|
173
166
|
return {
|
|
174
|
-
publicKey:
|
|
175
|
-
remoteKey:
|
|
176
|
-
headerKey: this.headerKey ?
|
|
177
|
-
nextHeaderKey:
|
|
178
|
-
chainKey:
|
|
167
|
+
publicKey: decodeBase64(this.publicKey),
|
|
168
|
+
remoteKey: decodeBase64(this.remoteKey),
|
|
169
|
+
headerKey: this.headerKey ? decodeBase64(this.headerKey) : undefined,
|
|
170
|
+
nextHeaderKey: decodeBase64(this.nextHeaderKey),
|
|
171
|
+
chainKey: decodeBase64(this.chainKey),
|
|
179
172
|
count: this.count,
|
|
180
173
|
previousCount: this.previousCount
|
|
181
174
|
};
|
|
182
175
|
}
|
|
183
176
|
static from(obj) {
|
|
184
177
|
//
|
|
185
|
-
const chain = new KeyChain(
|
|
178
|
+
const chain = new KeyChain(encodeBase64(obj.publicKey), encodeBase64(obj.remoteKey), encodeBase64(obj.chainKey), encodeBase64(obj.nextHeaderKey), obj.headerKey ? encodeBase64(obj.headerKey) : undefined, obj.previousCount);
|
|
186
179
|
chain._count = obj.count;
|
|
187
180
|
return chain;
|
|
188
181
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
* along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
18
18
|
*/
|
|
19
19
|
import { LocalStorage, Crypto, Database, KeyExchangeDataBundle } from "@freesignal/interfaces";
|
|
20
|
-
import { ExportedKeySession } from "./double-ratchet";
|
|
21
|
-
import { PrivateIdentityKey } from "./types";
|
|
22
|
-
import { BootstrapRequest, FreeSignalNode } from "./node";
|
|
20
|
+
import { ExportedKeySession } from "./double-ratchet.js";
|
|
21
|
+
import { PrivateIdentityKey } from "./types.js";
|
|
22
|
+
import { BootstrapRequest, FreeSignalNode } from "./node.js";
|
|
23
23
|
/**
|
|
24
24
|
* Generates identity key
|
|
25
25
|
*
|
|
@@ -35,4 +35,4 @@ export declare function createNode(storage: Database<{
|
|
|
35
35
|
bundles: LocalStorage<string, KeyExchangeDataBundle>;
|
|
36
36
|
bootstraps: LocalStorage<string, BootstrapRequest>;
|
|
37
37
|
}>, privateIdentityKey?: PrivateIdentityKey): FreeSignalNode;
|
|
38
|
-
export * from "./types";
|
|
38
|
+
export * from "./types.js";
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* FreeSignal Protocol
|
|
4
3
|
*
|
|
@@ -17,45 +16,26 @@
|
|
|
17
16
|
* You should have received a copy of the GNU General Public License
|
|
18
17
|
* along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
19
18
|
*/
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
24
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
25
|
-
}
|
|
26
|
-
Object.defineProperty(o, k2, desc);
|
|
27
|
-
}) : (function(o, m, k, k2) {
|
|
28
|
-
if (k2 === undefined) k2 = k;
|
|
29
|
-
o[k2] = m[k];
|
|
30
|
-
}));
|
|
31
|
-
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
32
|
-
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
33
|
-
};
|
|
34
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
35
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
36
|
-
};
|
|
37
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
-
exports.createIdentity = createIdentity;
|
|
39
|
-
exports.createNode = createNode;
|
|
40
|
-
const crypto_1 = __importDefault(require("@freesignal/crypto"));
|
|
41
|
-
const types_1 = require("./types");
|
|
42
|
-
const node_1 = require("./node");
|
|
19
|
+
import crypto from "@freesignal/crypto";
|
|
20
|
+
import { PrivateIdentityKey } from "./types.js";
|
|
21
|
+
import { FreeSignalNode } from "./node.js";
|
|
43
22
|
/**
|
|
44
23
|
* Generates identity key
|
|
45
24
|
*
|
|
46
25
|
* @param seed - Seed to generate the key.
|
|
47
26
|
* @returns An object containing readonly signing and box key pairs.
|
|
48
27
|
*/
|
|
49
|
-
function createIdentity(seed) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
|
|
28
|
+
export function createIdentity(seed) {
|
|
29
|
+
const seedLength = 32;
|
|
30
|
+
seed !== null && seed !== void 0 ? seed : (seed = crypto.randomBytes(seedLength));
|
|
31
|
+
const signatureSeed = crypto.hkdf(seed, new Uint8Array(seedLength).fill(0), "identity-ed25519", seedLength);
|
|
32
|
+
const exchangeSeed = crypto.hkdf(seed, new Uint8Array(crypto.ECDH.secretKeyLength).fill(0), "identity-x25519", crypto.ECDH.secretKeyLength);
|
|
33
|
+
const signatureKeyPair = crypto.EdDSA.keyPairFromSeed(signatureSeed);
|
|
34
|
+
const exchangeKeyPair = crypto.ECDH.keyPair(exchangeSeed);
|
|
35
|
+
return PrivateIdentityKey.from(signatureKeyPair.secretKey, exchangeKeyPair.secretKey);
|
|
56
36
|
}
|
|
57
37
|
/** */
|
|
58
|
-
function createNode(storage, privateIdentityKey) {
|
|
59
|
-
return new
|
|
38
|
+
export function createNode(storage, privateIdentityKey) {
|
|
39
|
+
return new FreeSignalNode(storage, privateIdentityKey);
|
|
60
40
|
}
|
|
61
|
-
|
|
41
|
+
export * from "./types.js";
|
package/dist/node.d.ts
CHANGED
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
* along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
18
18
|
*/
|
|
19
19
|
import { Database, LocalStorage, Crypto, KeyExchangeDataBundle, KeyExchangeData } from "@freesignal/interfaces";
|
|
20
|
-
import { Datagram, EncryptedDatagram, IdentityKey, PrivateIdentityKey, Protocols, UserId } from "./types";
|
|
21
|
-
import { KeyExchange } from "./x3dh";
|
|
22
|
-
import { ExportedKeySession, KeySession } from "./double-ratchet";
|
|
23
|
-
import EventEmitter,
|
|
20
|
+
import { Datagram, EncryptedDatagram, IdentityKey, PrivateIdentityKey, Protocols, UserId } from "./types.js";
|
|
21
|
+
import { KeyExchange } from "./x3dh.js";
|
|
22
|
+
import { ExportedKeySession, KeySession } from "./double-ratchet.js";
|
|
23
|
+
import { EventEmitter, EventCallback } from "easyemitter.ts";
|
|
24
24
|
export declare class BootstrapRequest extends EventEmitter<'change', BootstrapRequest> {
|
|
25
25
|
#private;
|
|
26
26
|
readonly senderId: UserId | string;
|
|
@@ -81,6 +81,7 @@ export declare class FreeSignalNode {
|
|
|
81
81
|
protected encrypt(receiverId: string | UserId, protocol: Protocols, data: Uint8Array): Promise<SendEventData>;
|
|
82
82
|
sendHandshake(data: KeyExchangeData): Promise<void>;
|
|
83
83
|
sendHandshake(session: KeySession): Promise<void>;
|
|
84
|
+
sendHandshake(userId: UserId | string): Promise<void>;
|
|
84
85
|
sendData<T>(receiverId: string | UserId, data: T): Promise<void>;
|
|
85
86
|
sendRelay(relayId: string | UserId, receiverId: string | UserId, data: Datagram): Promise<void>;
|
|
86
87
|
sendPing(receiverId: string | UserId): Promise<void>;
|
package/dist/node.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* FreeSignal Protocol
|
|
4
3
|
*
|
|
@@ -37,20 +36,15 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
|
|
|
37
36
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
38
37
|
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
39
38
|
};
|
|
40
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
41
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
42
|
-
};
|
|
43
39
|
var _BootstrapRequest_status;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const easyemitter_ts_1 = __importDefault(require("easyemitter.ts"));
|
|
53
|
-
class BootstrapRequest extends easyemitter_ts_1.default {
|
|
40
|
+
import { Datagram, decryptData, DiscoverType, encryptData, EncryptedDatagram, IdentityKey, Protocols, UserId } from "./types.js";
|
|
41
|
+
import { KeyExchange } from "./x3dh.js";
|
|
42
|
+
import { KeySession } from "./double-ratchet.js";
|
|
43
|
+
import { createIdentity } from "./index.js";
|
|
44
|
+
import { decodeData, encodeData, compareBytes, concatBytes, decodeBase64 } from "@freesignal/utils";
|
|
45
|
+
import crypto from "@freesignal/crypto";
|
|
46
|
+
import { EventEmitter } from "easyemitter.ts";
|
|
47
|
+
export class BootstrapRequest extends EventEmitter {
|
|
54
48
|
constructor(senderId, data) {
|
|
55
49
|
super();
|
|
56
50
|
this.senderId = senderId;
|
|
@@ -75,24 +69,23 @@ class BootstrapRequest extends easyemitter_ts_1.default {
|
|
|
75
69
|
}
|
|
76
70
|
}
|
|
77
71
|
}
|
|
78
|
-
exports.BootstrapRequest = BootstrapRequest;
|
|
79
72
|
_BootstrapRequest_status = new WeakMap();
|
|
80
|
-
class FreeSignalNode {
|
|
73
|
+
export class FreeSignalNode {
|
|
81
74
|
constructor(storage, privateIdentityKey) {
|
|
82
75
|
this.discovers = new Set();
|
|
83
|
-
this.emitter = new
|
|
76
|
+
this.emitter = new EventEmitter();
|
|
84
77
|
this.messageHandler = (data) => this.onMessage({ session: data.session, payload: data.payload });
|
|
85
78
|
this.sendHandler = (data) => this.onSend(data.datagram.toBytes());
|
|
86
|
-
this.handshakeHandler = (data) => { var _a; return this.onHandshake(
|
|
79
|
+
this.handshakeHandler = (data) => { var _a; return this.onHandshake(UserId.from((_a = data.session) === null || _a === void 0 ? void 0 : _a.userId)); };
|
|
87
80
|
this.bootstrapHandler = (data) => this.onRequest(data.request);
|
|
88
81
|
this.onMessage = () => { };
|
|
89
82
|
this.onSend = () => { };
|
|
90
83
|
this.onHandshake = () => { };
|
|
91
84
|
this.onRequest = () => { };
|
|
92
|
-
this.privateIdentityKey = privateIdentityKey !== null && privateIdentityKey !== void 0 ? privateIdentityKey :
|
|
85
|
+
this.privateIdentityKey = privateIdentityKey !== null && privateIdentityKey !== void 0 ? privateIdentityKey : createIdentity();
|
|
93
86
|
this.sessions = new SessionMap(storage.sessions);
|
|
94
87
|
this.users = storage.users;
|
|
95
|
-
this.keyExchange = new
|
|
88
|
+
this.keyExchange = new KeyExchange(storage.keyExchange, this.privateIdentityKey);
|
|
96
89
|
this.bundles = storage.bundles;
|
|
97
90
|
this.bootstraps = storage.bootstraps;
|
|
98
91
|
this.emitter.on('message', this.messageHandler);
|
|
@@ -126,39 +119,45 @@ class FreeSignalNode {
|
|
|
126
119
|
const session = yield this.sessions.get(sessionTag);
|
|
127
120
|
if (!session)
|
|
128
121
|
throw new Error("Session not found for sessionTag: " + sessionTag);
|
|
129
|
-
const encrypted =
|
|
122
|
+
const encrypted = encryptData(session, data);
|
|
130
123
|
this.sessions.set(receiverId.toString(), session);
|
|
131
|
-
return { session, userId:
|
|
124
|
+
return { session, userId: UserId.from(receiverId), datagram: new EncryptedDatagram(protocol, session.sessionTag, encrypted).sign(this.privateIdentityKey.signatureKey) };
|
|
132
125
|
});
|
|
133
126
|
}
|
|
134
127
|
sendHandshake(data) {
|
|
135
128
|
return __awaiter(this, void 0, void 0, function* () {
|
|
136
|
-
if (data instanceof
|
|
129
|
+
if (data instanceof UserId || typeof data === 'string') {
|
|
130
|
+
const session = yield this.sessions.get(data.toString());
|
|
131
|
+
if (!session)
|
|
132
|
+
throw new Error("Session not found for userId: " + data.toString());
|
|
133
|
+
data = session;
|
|
134
|
+
}
|
|
135
|
+
if (data instanceof KeySession) {
|
|
137
136
|
//console.debug("Sending Handshake Ack");
|
|
138
137
|
const session = yield this.sessions.get(data.sessionTag);
|
|
139
138
|
if (!session)
|
|
140
139
|
throw new Error("Session not found for sessionTag: " + data.sessionTag);
|
|
141
|
-
this.emitter.emit('send', yield this.encrypt(session.userId,
|
|
140
|
+
this.emitter.emit('send', yield this.encrypt(session.userId, Protocols.HANDSHAKE, crypto.ECDH.scalarMult(this.privateIdentityKey.exchangeKey, session.identityKey.exchangeKey)));
|
|
142
141
|
return;
|
|
143
142
|
}
|
|
144
143
|
//console.debug("Sending Handshake Syn");
|
|
145
|
-
const { session, message } = yield this.keyExchange.digestData(data,
|
|
144
|
+
const { session, message } = yield this.keyExchange.digestData(data, encodeData(yield this.keyExchange.generateBundle()));
|
|
146
145
|
yield this.sessions.set(session.sessionTag, session);
|
|
147
146
|
yield this.users.set(session.userId.toString(), session.sessionTag);
|
|
148
|
-
const datagram = new
|
|
147
|
+
const datagram = new Datagram(Protocols.HANDSHAKE, encodeData(message), session.sessionTag).sign(this.privateIdentityKey.signatureKey);
|
|
149
148
|
this.emitter.emit('send', { session, datagram, userId: session.userId });
|
|
150
149
|
});
|
|
151
150
|
}
|
|
152
151
|
sendData(receiverId, data) {
|
|
153
152
|
return __awaiter(this, void 0, void 0, function* () {
|
|
154
153
|
//console.debug("Sending Data");
|
|
155
|
-
this.emitter.emit('send', yield this.encrypt(receiverId,
|
|
154
|
+
this.emitter.emit('send', yield this.encrypt(receiverId, Protocols.MESSAGE, encodeData(data)));
|
|
156
155
|
});
|
|
157
156
|
}
|
|
158
157
|
sendRelay(relayId, receiverId, data) {
|
|
159
158
|
return __awaiter(this, void 0, void 0, function* () {
|
|
160
159
|
//console.debug("Sending Relay");
|
|
161
|
-
this.emitter.emit('send', yield this.encrypt(relayId,
|
|
160
|
+
this.emitter.emit('send', yield this.encrypt(relayId, Protocols.RELAY, concatBytes(UserId.from(receiverId).toBytes(), data.toBytes())));
|
|
162
161
|
});
|
|
163
162
|
}
|
|
164
163
|
sendPing(receiverId) {
|
|
@@ -170,28 +169,28 @@ class FreeSignalNode {
|
|
|
170
169
|
const session = yield this.sessions.get(sessionTag);
|
|
171
170
|
if (!session)
|
|
172
171
|
throw new Error("Session not found for sessionTag: " + sessionTag);
|
|
173
|
-
const datagram = new
|
|
172
|
+
const datagram = new Datagram(Protocols.PING, undefined, session.sessionTag);
|
|
174
173
|
this.emitter.emit('send', { session, datagram, userId: session.userId });
|
|
175
174
|
});
|
|
176
175
|
}
|
|
177
176
|
sendDiscover(receiverId, discoverId) {
|
|
178
177
|
return __awaiter(this, void 0, void 0, function* () {
|
|
179
178
|
//console.debug("Sending Discover");
|
|
180
|
-
if (receiverId instanceof
|
|
179
|
+
if (receiverId instanceof UserId)
|
|
181
180
|
receiverId = receiverId.toString();
|
|
182
|
-
if (discoverId instanceof
|
|
181
|
+
if (discoverId instanceof UserId)
|
|
183
182
|
discoverId = discoverId.toString();
|
|
184
183
|
const message = {
|
|
185
|
-
type:
|
|
184
|
+
type: DiscoverType.REQUEST,
|
|
186
185
|
discoverId
|
|
187
186
|
};
|
|
188
187
|
this.discovers.add(receiverId);
|
|
189
|
-
this.emitter.emit('send', yield this.encrypt(receiverId,
|
|
188
|
+
this.emitter.emit('send', yield this.encrypt(receiverId, Protocols.DISCOVER, encodeData(message)));
|
|
190
189
|
});
|
|
191
190
|
}
|
|
192
191
|
packBootstrap() {
|
|
193
192
|
return __awaiter(this, void 0, void 0, function* () {
|
|
194
|
-
return new
|
|
193
|
+
return new Datagram(Protocols.BOOTSTRAP, encodeData(yield this.keyExchange.generateData()));
|
|
195
194
|
});
|
|
196
195
|
}
|
|
197
196
|
sendBootstrap(receiverId) {
|
|
@@ -200,12 +199,12 @@ class FreeSignalNode {
|
|
|
200
199
|
if (yield this.sessions.has(receiverId.toString()))
|
|
201
200
|
throw new Error("Session exists");
|
|
202
201
|
const datagram = yield this.packBootstrap();
|
|
203
|
-
this.emitter.emit('send', { datagram, userId:
|
|
202
|
+
this.emitter.emit('send', { datagram, userId: UserId.from(receiverId) });
|
|
204
203
|
});
|
|
205
204
|
}
|
|
206
205
|
decrypt(datagram) {
|
|
207
206
|
return __awaiter(this, void 0, void 0, function* () {
|
|
208
|
-
datagram =
|
|
207
|
+
datagram = EncryptedDatagram.from(datagram);
|
|
209
208
|
if (!datagram.sessionTag)
|
|
210
209
|
throw new Error("Datagram not encrypted");
|
|
211
210
|
const session = yield this.sessions.get(datagram.sessionTag);
|
|
@@ -215,14 +214,14 @@ class FreeSignalNode {
|
|
|
215
214
|
throw new Error("Signature not verified");
|
|
216
215
|
if (!datagram.payload)
|
|
217
216
|
throw new Error("Missing payload");
|
|
218
|
-
const decrypted =
|
|
217
|
+
const decrypted = decryptData(session, datagram.payload);
|
|
219
218
|
this.sessions.set(datagram.sessionTag, session);
|
|
220
219
|
return { session, payload: decrypted };
|
|
221
220
|
});
|
|
222
221
|
}
|
|
223
222
|
openHandshake(datagram) {
|
|
224
223
|
return __awaiter(this, void 0, void 0, function* () {
|
|
225
|
-
const encrypted =
|
|
224
|
+
const encrypted = EncryptedDatagram.from(datagram);
|
|
226
225
|
if (!encrypted.payload)
|
|
227
226
|
throw new Error("Missing payload");
|
|
228
227
|
if (yield this.sessions.has(encrypted.sessionTag)) {
|
|
@@ -231,28 +230,28 @@ class FreeSignalNode {
|
|
|
231
230
|
const { payload } = yield this.decrypt(encrypted);
|
|
232
231
|
if (!session)
|
|
233
232
|
throw new Error("Session not found for sessionTag: " + encrypted.sessionTag);
|
|
234
|
-
if (!
|
|
233
|
+
if (!compareBytes(payload, crypto.ECDH.scalarMult(this.privateIdentityKey.exchangeKey, session.identityKey.exchangeKey)))
|
|
235
234
|
throw new Error("Error validating handshake data");
|
|
236
235
|
return 'ack';
|
|
237
236
|
}
|
|
238
237
|
//console.debug("Opening Handshake Syn");
|
|
239
|
-
const data =
|
|
240
|
-
if (!encrypted.verify(
|
|
238
|
+
const data = decodeData(encrypted.payload);
|
|
239
|
+
if (!encrypted.verify(IdentityKey.from(data.identityKey).signatureKey))
|
|
241
240
|
throw new Error("Signature not verified");
|
|
242
241
|
const { session, associatedData } = yield this.keyExchange.digestMessage(data);
|
|
243
242
|
yield this.sessions.set(session.sessionTag, session);
|
|
244
243
|
yield this.users.set(session.userId.toString(), session.sessionTag);
|
|
245
|
-
yield this.bundles.set(session.userId.toString(),
|
|
244
|
+
yield this.bundles.set(session.userId.toString(), decodeData(associatedData));
|
|
246
245
|
return 'syn';
|
|
247
246
|
});
|
|
248
247
|
}
|
|
249
248
|
open(datagram) {
|
|
250
249
|
return __awaiter(this, void 0, void 0, function* () {
|
|
251
250
|
if (datagram instanceof Uint8Array)
|
|
252
|
-
datagram =
|
|
251
|
+
datagram = Datagram.from(datagram);
|
|
253
252
|
switch (datagram.protocol) {
|
|
254
|
-
case
|
|
255
|
-
const encrypted =
|
|
253
|
+
case Protocols.HANDSHAKE: {
|
|
254
|
+
const encrypted = EncryptedDatagram.from(datagram);
|
|
256
255
|
if (!encrypted.payload)
|
|
257
256
|
throw new Error("Missing payload");
|
|
258
257
|
if ((yield this.openHandshake(datagram)) === 'ack')
|
|
@@ -264,28 +263,28 @@ class FreeSignalNode {
|
|
|
264
263
|
this.emitter.emit('handshake', { session });
|
|
265
264
|
return;
|
|
266
265
|
}
|
|
267
|
-
case
|
|
266
|
+
case Protocols.MESSAGE:
|
|
268
267
|
//console.debug("Opening Message");
|
|
269
268
|
this.emitter.emit('message', yield this.decrypt(datagram));
|
|
270
269
|
return;
|
|
271
|
-
case
|
|
270
|
+
case Protocols.RELAY: {
|
|
272
271
|
//console.debug("Opening Relay");
|
|
273
272
|
const opened = yield this.decrypt(datagram);
|
|
274
|
-
const userId =
|
|
273
|
+
const userId = decodeBase64(opened.payload.subarray(0, UserId.keyLength));
|
|
275
274
|
const sessionTag = yield this.users.get(userId);
|
|
276
275
|
if (!sessionTag)
|
|
277
276
|
throw new Error("Session not found for user: " + userId);
|
|
278
277
|
const session = yield this.sessions.get(sessionTag);
|
|
279
278
|
if (!session)
|
|
280
279
|
throw new Error("Session not found for sessionTag: " + datagram.sessionTag);
|
|
281
|
-
this.emitter.emit('send', { session, datagram:
|
|
280
|
+
this.emitter.emit('send', { session, datagram: Datagram.from(opened.payload.slice(UserId.keyLength)), userId: session.userId });
|
|
282
281
|
return;
|
|
283
282
|
}
|
|
284
|
-
case
|
|
283
|
+
case Protocols.DISCOVER: {
|
|
285
284
|
//console.debug("Opening Discover");
|
|
286
285
|
const { session, payload } = yield this.decrypt(datagram);
|
|
287
|
-
const message =
|
|
288
|
-
if (message.type ===
|
|
286
|
+
const message = decodeData(payload);
|
|
287
|
+
if (message.type === DiscoverType.REQUEST && message.discoverId && !(yield this.sessions.has(message.discoverId))) {
|
|
289
288
|
let data;
|
|
290
289
|
if (message.discoverId === this.userId.toString()) {
|
|
291
290
|
data = yield this.keyExchange.generateData();
|
|
@@ -308,22 +307,22 @@ class FreeSignalNode {
|
|
|
308
307
|
onetimePreKey
|
|
309
308
|
};
|
|
310
309
|
}
|
|
311
|
-
const response = { type:
|
|
312
|
-
this.emitter.emit('send', yield this.encrypt(session.userId,
|
|
310
|
+
const response = { type: DiscoverType.RESPONSE, discoverId: message.discoverId, data };
|
|
311
|
+
this.emitter.emit('send', yield this.encrypt(session.userId, Protocols.DISCOVER, encodeData(response)));
|
|
313
312
|
}
|
|
314
|
-
else if (message.type ===
|
|
313
|
+
else if (message.type === DiscoverType.RESPONSE && this.discovers.has(message.discoverId)) {
|
|
315
314
|
this.discovers.delete(message.discoverId);
|
|
316
315
|
if (message.data)
|
|
317
316
|
yield this.sendHandshake(message.data);
|
|
318
317
|
}
|
|
319
318
|
return;
|
|
320
319
|
}
|
|
321
|
-
case
|
|
320
|
+
case Protocols.BOOTSTRAP: {
|
|
322
321
|
//console.debug("Opening Bootstrap");
|
|
323
322
|
if (!datagram.payload)
|
|
324
323
|
throw new Error("Invalid Bootstrap");
|
|
325
|
-
const keyExchangeData =
|
|
326
|
-
const userId =
|
|
324
|
+
const keyExchangeData = decodeData(datagram.payload);
|
|
325
|
+
const userId = UserId.fromKey(keyExchangeData.identityKey);
|
|
327
326
|
const request = new BootstrapRequest(userId, keyExchangeData);
|
|
328
327
|
let sended = false;
|
|
329
328
|
request.onChange = (request) => {
|
|
@@ -336,8 +335,8 @@ class FreeSignalNode {
|
|
|
336
335
|
this.emitter.emit('bootstrap', { request });
|
|
337
336
|
return;
|
|
338
337
|
}
|
|
339
|
-
case
|
|
340
|
-
datagram =
|
|
338
|
+
case Protocols.PING:
|
|
339
|
+
datagram = EncryptedDatagram.from(datagram);
|
|
341
340
|
const session = yield this.sessions.get(datagram.sessionTag);
|
|
342
341
|
if (!session)
|
|
343
342
|
throw new Error("Session not found for sessionTag: " + datagram.sessionTag);
|
|
@@ -349,7 +348,6 @@ class FreeSignalNode {
|
|
|
349
348
|
});
|
|
350
349
|
}
|
|
351
350
|
}
|
|
352
|
-
exports.FreeSignalNode = FreeSignalNode;
|
|
353
351
|
class SessionMap {
|
|
354
352
|
constructor(storage, maxSize = 50) {
|
|
355
353
|
this.storage = storage;
|
|
@@ -367,7 +365,7 @@ class SessionMap {
|
|
|
367
365
|
const sessionData = yield this.storage.get(key);
|
|
368
366
|
if (!sessionData)
|
|
369
367
|
return undefined;
|
|
370
|
-
return
|
|
368
|
+
return KeySession.from(sessionData);
|
|
371
369
|
}
|
|
372
370
|
return session;
|
|
373
371
|
});
|
package/dist/types.d.ts
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
18
18
|
*/
|
|
19
19
|
import type { LocalStorage, Encodable, KeyExchangeData } from "@freesignal/interfaces";
|
|
20
|
-
import { EncryptionKeys, KeySession } from "./double-ratchet";
|
|
20
|
+
import { EncryptionKeys, KeySession } from "./double-ratchet.js";
|
|
21
21
|
export declare function encryptData(session: KeySession, data: Uint8Array): EncryptedData;
|
|
22
22
|
export declare function decryptData(session: KeySession, encryptedData: Uint8Array): Uint8Array;
|
|
23
23
|
export declare class UserId implements Encodable {
|