@freesignal/protocol 0.9.0 → 0.9.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/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/node.d.ts +14 -14
- package/dist/node.js +221 -204
- package/dist/x3dh.d.ts +1 -4
- package/dist/x3dh.js +1 -1
- package/package.json +1 -1
package/dist/index.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 { PrivateIdentityKey } from "./types.js";
|
|
20
|
-
import { FreeSignalNode } from "./node.js";
|
|
20
|
+
import { FreeSignalNode, FreeSignalNodeState } from "./node.js";
|
|
21
21
|
/**
|
|
22
22
|
* Generates identity key
|
|
23
23
|
*
|
|
@@ -26,5 +26,5 @@ import { FreeSignalNode } from "./node.js";
|
|
|
26
26
|
*/
|
|
27
27
|
export declare function createIdentity(seed?: Uint8Array): PrivateIdentityKey;
|
|
28
28
|
/** */
|
|
29
|
-
export declare function createNode(
|
|
29
|
+
export declare function createNode(args?: Partial<FreeSignalNodeState>): FreeSignalNode;
|
|
30
30
|
export * from "./types.js";
|
package/dist/index.js
CHANGED
|
@@ -35,7 +35,7 @@ export function createIdentity(seed) {
|
|
|
35
35
|
return PrivateIdentityKey.from(signatureKeyPair.secretKey, exchangeKeyPair.secretKey);
|
|
36
36
|
}
|
|
37
37
|
/** */
|
|
38
|
-
export function createNode(
|
|
39
|
-
return new FreeSignalNode(
|
|
38
|
+
export function createNode(args) {
|
|
39
|
+
return new FreeSignalNode(args);
|
|
40
40
|
}
|
|
41
41
|
export * from "./types.js";
|
package/dist/node.d.ts
CHANGED
|
@@ -16,9 +16,9 @@
|
|
|
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 { KeyExchangeDataBundle, KeyExchangeData } from "@freesignal/interfaces";
|
|
19
|
+
import { KeyExchangeDataBundle, KeyExchangeData, Crypto } from "@freesignal/interfaces";
|
|
20
20
|
import { Datagram, EncryptedDatagram, IdentityKey, PrivateIdentityKey, Protocols, UserId } from "./types.js";
|
|
21
|
-
import { KeyExchange
|
|
21
|
+
import { KeyExchange } from "./x3dh.js";
|
|
22
22
|
import { ExportedKeySession as KeySessionState, KeySession } from "./double-ratchet.js";
|
|
23
23
|
import { EventEmitter, EventCallback } from "easyemitter.ts";
|
|
24
24
|
export declare class BootstrapRequest extends EventEmitter<'change', BootstrapRequest> {
|
|
@@ -55,7 +55,7 @@ export type FreeSignalNodeState = {
|
|
|
55
55
|
sessions: Array<[string, KeySessionState]>;
|
|
56
56
|
users: Array<[string, string]>;
|
|
57
57
|
bundles: Array<[string, KeyExchangeDataBundle]>;
|
|
58
|
-
keyExchange:
|
|
58
|
+
keyExchange: Array<[string, Crypto.KeyPair]>;
|
|
59
59
|
};
|
|
60
60
|
export declare class FreeSignalNode {
|
|
61
61
|
protected readonly privateIdentityKey: PrivateIdentityKey;
|
|
@@ -65,7 +65,7 @@ export declare class FreeSignalNode {
|
|
|
65
65
|
protected readonly keyExchange: KeyExchange;
|
|
66
66
|
protected readonly bootstraps: Map<string, BootstrapRequest>;
|
|
67
67
|
protected readonly emitter: EventEmitter<"error" | "debug" | "send" | "handshake" | "message" | "bootstrap", NodeEventData>;
|
|
68
|
-
constructor(privateIdentityKey?:
|
|
68
|
+
constructor({ privateIdentityKey, sessions, users, bundles, keyExchange }?: Partial<FreeSignalNodeState>);
|
|
69
69
|
protected messageHandler: EventCallback<NodeEventData, typeof this.emitter>;
|
|
70
70
|
protected sendHandler: EventCallback<NodeEventData, typeof this.emitter>;
|
|
71
71
|
protected handshakeHandler: EventCallback<NodeEventData, typeof this.emitter>;
|
|
@@ -82,16 +82,16 @@ export declare class FreeSignalNode {
|
|
|
82
82
|
waitHandshaked(userId: UserId | string, timeout?: number): Promise<void>;
|
|
83
83
|
get identityKey(): IdentityKey;
|
|
84
84
|
get userId(): UserId;
|
|
85
|
-
protected encrypt(receiverId: string | UserId, protocol: Protocols, data: Uint8Array): SendEventData
|
|
86
|
-
sendHandshake(data: KeyExchangeData): void
|
|
87
|
-
sendHandshake(session: KeySession): void
|
|
88
|
-
sendHandshake(userId: UserId | string): void
|
|
89
|
-
sendData<T>(receiverId: string | UserId, data: T): void
|
|
90
|
-
sendRelay(relayId: string | UserId, receiverId: string | UserId, data: Datagram): void
|
|
85
|
+
protected encrypt(receiverId: string | UserId, protocol: Protocols, data: Uint8Array): Promise<SendEventData>;
|
|
86
|
+
sendHandshake(data: KeyExchangeData): Promise<void>;
|
|
87
|
+
sendHandshake(session: KeySession): Promise<void>;
|
|
88
|
+
sendHandshake(userId: UserId | string): Promise<void>;
|
|
89
|
+
sendData<T>(receiverId: string | UserId, data: T): Promise<void>;
|
|
90
|
+
sendRelay(relayId: string | UserId, receiverId: string | UserId, data: Datagram): Promise<void>;
|
|
91
91
|
packBootstrap(): Datagram;
|
|
92
|
-
sendBootstrap(receiverId: string | UserId): void
|
|
93
|
-
protected decrypt(datagram: EncryptedDatagram | Datagram | Uint8Array): MessageEventData
|
|
94
|
-
protected openHandshake(datagram: Datagram | EncryptedDatagram | Uint8Array):
|
|
95
|
-
protected open(datagram: Datagram | EncryptedDatagram | Uint8Array): void
|
|
92
|
+
sendBootstrap(receiverId: string | UserId): Promise<void>;
|
|
93
|
+
protected decrypt(datagram: EncryptedDatagram | Datagram | Uint8Array): Promise<MessageEventData>;
|
|
94
|
+
protected openHandshake(datagram: Datagram | EncryptedDatagram | Uint8Array): Promise<"syn" | "ack">;
|
|
95
|
+
protected open(datagram: Datagram | EncryptedDatagram | Uint8Array): Promise<void>;
|
|
96
96
|
toJSON(): FreeSignalNodeState;
|
|
97
97
|
}
|
package/dist/node.js
CHANGED
|
@@ -37,7 +37,7 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
|
|
|
37
37
|
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
38
38
|
};
|
|
39
39
|
var _BootstrapRequest_status;
|
|
40
|
-
import { Datagram, decryptData, encryptData, EncryptedDatagram, IdentityKey, Protocols, UserId } from "./types.js";
|
|
40
|
+
import { Datagram, decryptData, encryptData, EncryptedDatagram, IdentityKey, PrivateIdentityKey, Protocols, UserId } from "./types.js";
|
|
41
41
|
import { KeyExchange } from "./x3dh.js";
|
|
42
42
|
import { KeySession } from "./double-ratchet.js";
|
|
43
43
|
import { createIdentity } from "./index.js";
|
|
@@ -71,10 +71,8 @@ export class BootstrapRequest extends EventEmitter {
|
|
|
71
71
|
}
|
|
72
72
|
_BootstrapRequest_status = new WeakMap();
|
|
73
73
|
export class FreeSignalNode {
|
|
74
|
-
constructor(privateIdentityKey) {
|
|
75
|
-
|
|
76
|
-
this.users = new Map();
|
|
77
|
-
this.bundles = new Map();
|
|
74
|
+
constructor({ privateIdentityKey, sessions, users, bundles, keyExchange } = {}) {
|
|
75
|
+
var _a;
|
|
78
76
|
this.bootstraps = new Map();
|
|
79
77
|
this.emitter = new EventEmitter();
|
|
80
78
|
this.messageHandler = (data) => this.onMessage({ session: data.session, payload: data.payload });
|
|
@@ -87,8 +85,11 @@ export class FreeSignalNode {
|
|
|
87
85
|
this.onRequest = () => { };
|
|
88
86
|
this.onError = () => { };
|
|
89
87
|
this.onDebug = () => { };
|
|
90
|
-
this.privateIdentityKey = privateIdentityKey
|
|
91
|
-
this.
|
|
88
|
+
this.privateIdentityKey = privateIdentityKey ? PrivateIdentityKey.from(privateIdentityKey) : createIdentity();
|
|
89
|
+
this.sessions = new Map((_a = sessions === null || sessions === void 0 ? void 0 : sessions.map(([key, value]) => [key, KeySession.from(value)])) !== null && _a !== void 0 ? _a : []);
|
|
90
|
+
this.users = new Map(users);
|
|
91
|
+
this.bundles = new Map(bundles);
|
|
92
|
+
this.keyExchange = new KeyExchange({ privateIdentityKey: this.privateIdentityKey, storage: keyExchange });
|
|
92
93
|
this.emitter.on('message', this.messageHandler);
|
|
93
94
|
this.emitter.on('send', this.sendHandler);
|
|
94
95
|
this.emitter.on('handshake', this.handshakeHandler);
|
|
@@ -122,57 +123,65 @@ export class FreeSignalNode {
|
|
|
122
123
|
return this.identityKey.userId;
|
|
123
124
|
}
|
|
124
125
|
encrypt(receiverId, protocol, data) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
126
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
127
|
+
try {
|
|
128
|
+
const sessionTag = this.users.get(receiverId.toString());
|
|
129
|
+
if (!sessionTag)
|
|
130
|
+
throw new Error("User not found: " + receiverId);
|
|
131
|
+
const session = this.sessions.get(sessionTag);
|
|
132
|
+
if (!session)
|
|
133
|
+
throw new Error("Session not found for sessionTag: " + sessionTag);
|
|
134
|
+
const encrypted = encryptData(session, data);
|
|
135
|
+
this.sessions.set(receiverId.toString(), session);
|
|
136
|
+
return { session, userId: UserId.from(receiverId), datagram: new EncryptedDatagram(protocol, session.sessionTag, encrypted).sign(this.privateIdentityKey.signatureKey) };
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
this.error(error);
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
});
|
|
140
143
|
}
|
|
141
144
|
sendHandshake(data) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
146
|
+
try {
|
|
147
|
+
if (data instanceof UserId || typeof data === 'string') {
|
|
148
|
+
const session = this.sessions.get(data.toString());
|
|
149
|
+
if (!session)
|
|
150
|
+
throw new Error("Session not found for userId: " + data.toString());
|
|
151
|
+
data = session;
|
|
152
|
+
}
|
|
153
|
+
if (data instanceof KeySession) {
|
|
154
|
+
//console.debug("Sending Handshake Ack");
|
|
155
|
+
const session = this.sessions.get(data.sessionTag);
|
|
156
|
+
if (!session)
|
|
157
|
+
throw new Error("Session not found for sessionTag: " + data.sessionTag);
|
|
158
|
+
this.emitter.emit('send', yield this.encrypt(session.userId, Protocols.HANDSHAKE, crypto.ECDH.scalarMult(this.privateIdentityKey.exchangeKey, session.identityKey.exchangeKey)));
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
148
161
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if (!session)
|
|
153
|
-
throw new Error("Session not found for sessionTag: " + data.sessionTag);
|
|
154
|
-
this.emitter.emit('send', this.encrypt(session.userId, Protocols.HANDSHAKE, crypto.ECDH.scalarMult(this.privateIdentityKey.exchangeKey, session.identityKey.exchangeKey)));
|
|
155
|
-
return;
|
|
162
|
+
catch (error) {
|
|
163
|
+
this.error(error);
|
|
164
|
+
throw error;
|
|
156
165
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
this.
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
this.sessions.set(session.sessionTag, session);
|
|
165
|
-
this.users.set(session.userId.toString(), session.sessionTag);
|
|
166
|
-
const datagram = new Datagram(Protocols.HANDSHAKE, encodeData(message), session.sessionTag).sign(this.privateIdentityKey.signatureKey);
|
|
167
|
-
this.emitter.emit('send', { session, datagram, userId: session.userId });
|
|
166
|
+
//console.debug("Sending Handshake Syn");
|
|
167
|
+
const { session, message } = this.keyExchange.digestData(data, encodeData(this.keyExchange.generateBundle()));
|
|
168
|
+
this.sessions.set(session.sessionTag, session);
|
|
169
|
+
this.users.set(session.userId.toString(), session.sessionTag);
|
|
170
|
+
const datagram = new Datagram(Protocols.HANDSHAKE, encodeData(message), session.sessionTag).sign(this.privateIdentityKey.signatureKey);
|
|
171
|
+
this.emitter.emit('send', { session, datagram, userId: session.userId });
|
|
172
|
+
});
|
|
168
173
|
}
|
|
169
174
|
sendData(receiverId, data) {
|
|
170
|
-
|
|
171
|
-
|
|
175
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
176
|
+
//console.debug("Sending Data");
|
|
177
|
+
this.emitter.emit('send', yield this.encrypt(receiverId, Protocols.MESSAGE, encodeData(data)));
|
|
178
|
+
});
|
|
172
179
|
}
|
|
173
180
|
sendRelay(relayId, receiverId, data) {
|
|
174
|
-
|
|
175
|
-
|
|
181
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
182
|
+
//console.debug("Sending Relay");
|
|
183
|
+
this.emitter.emit('send', yield this.encrypt(relayId, Protocols.RELAY, concatBytes(UserId.from(receiverId).toBytes(), data.toBytes())));
|
|
184
|
+
});
|
|
176
185
|
}
|
|
177
186
|
/*public async sendPing(receiverId: string | UserId): Promise<void> {
|
|
178
187
|
//console.debug("Sending Ping");
|
|
@@ -203,173 +212,181 @@ export class FreeSignalNode {
|
|
|
203
212
|
return new Datagram(Protocols.BOOTSTRAP, encodeData(this.keyExchange.generateData()));
|
|
204
213
|
}
|
|
205
214
|
sendBootstrap(receiverId) {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
215
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
216
|
+
try {
|
|
217
|
+
//console.debug("Sending Bootstrap");
|
|
218
|
+
if (this.sessions.has(receiverId.toString()))
|
|
219
|
+
throw new Error("Session exists");
|
|
220
|
+
const datagram = this.packBootstrap();
|
|
221
|
+
this.emitter.emit('send', { datagram, userId: UserId.from(receiverId) });
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
this.error(error);
|
|
225
|
+
throw error;
|
|
226
|
+
}
|
|
227
|
+
});
|
|
217
228
|
}
|
|
218
229
|
decrypt(datagram) {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
throw new Error("Session not found for sessionTag: " + datagram.sessionTag);
|
|
226
|
-
if (!datagram.verify(session.identityKey.signatureKey))
|
|
227
|
-
throw new Error("Signature not verified");
|
|
228
|
-
if (!datagram.payload)
|
|
229
|
-
throw new Error("Missing payload");
|
|
230
|
-
const decrypted = decryptData(session, datagram.payload);
|
|
231
|
-
this.sessions.set(datagram.sessionTag, session);
|
|
232
|
-
return { session, payload: decrypted };
|
|
233
|
-
}
|
|
234
|
-
catch (error) {
|
|
235
|
-
this.error(error);
|
|
236
|
-
throw error;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
openHandshake(datagram) {
|
|
240
|
-
try {
|
|
241
|
-
const encrypted = EncryptedDatagram.from(datagram);
|
|
242
|
-
if (!encrypted.payload)
|
|
243
|
-
throw new Error("Missing payload");
|
|
244
|
-
if (this.sessions.has(encrypted.sessionTag)) {
|
|
245
|
-
//console.debug("Opening Handshake Ack");
|
|
246
|
-
const session = this.sessions.get(encrypted.sessionTag);
|
|
247
|
-
const { payload } = this.decrypt(encrypted);
|
|
230
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
231
|
+
try {
|
|
232
|
+
datagram = EncryptedDatagram.from(datagram);
|
|
233
|
+
if (!datagram.sessionTag)
|
|
234
|
+
throw new Error("Datagram not encrypted");
|
|
235
|
+
const session = this.sessions.get(datagram.sessionTag);
|
|
248
236
|
if (!session)
|
|
249
|
-
throw new Error("Session not found for sessionTag: " +
|
|
250
|
-
if (!
|
|
251
|
-
throw new Error("
|
|
252
|
-
|
|
237
|
+
throw new Error("Session not found for sessionTag: " + datagram.sessionTag);
|
|
238
|
+
if (!datagram.verify(session.identityKey.signatureKey))
|
|
239
|
+
throw new Error("Signature not verified");
|
|
240
|
+
if (!datagram.payload)
|
|
241
|
+
throw new Error("Missing payload");
|
|
242
|
+
const decrypted = decryptData(session, datagram.payload);
|
|
243
|
+
this.sessions.set(datagram.sessionTag, session);
|
|
244
|
+
return { session, payload: decrypted };
|
|
253
245
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
this.sessions.set(session.sessionTag, session);
|
|
260
|
-
this.users.set(session.userId.toString(), session.sessionTag);
|
|
261
|
-
this.bundles.set(session.userId.toString(), decodeData(associatedData));
|
|
262
|
-
return 'syn';
|
|
263
|
-
}
|
|
264
|
-
catch (error) {
|
|
265
|
-
this.error(error);
|
|
266
|
-
throw error;
|
|
267
|
-
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
this.error(error);
|
|
248
|
+
throw error;
|
|
249
|
+
}
|
|
250
|
+
});
|
|
268
251
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
throw new Error("Missing payload");
|
|
278
|
-
const handshakeState = this.openHandshake(datagram);
|
|
252
|
+
openHandshake(datagram) {
|
|
253
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
254
|
+
try {
|
|
255
|
+
const encrypted = EncryptedDatagram.from(datagram);
|
|
256
|
+
if (!encrypted.payload)
|
|
257
|
+
throw new Error("Missing payload");
|
|
258
|
+
if (this.sessions.has(encrypted.sessionTag)) {
|
|
259
|
+
//console.debug("Opening Handshake Ack");
|
|
279
260
|
const session = this.sessions.get(encrypted.sessionTag);
|
|
261
|
+
const { payload } = yield this.decrypt(encrypted);
|
|
280
262
|
if (!session)
|
|
281
263
|
throw new Error("Session not found for sessionTag: " + encrypted.sessionTag);
|
|
282
|
-
if (
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
case Protocols.MESSAGE:
|
|
288
|
-
//console.debug("Opening Message");
|
|
289
|
-
this.emitter.emit('message', this.decrypt(datagram));
|
|
290
|
-
return;
|
|
291
|
-
case Protocols.RELAY: {
|
|
292
|
-
//console.debug("Opening Relay");
|
|
293
|
-
const opened = this.decrypt(datagram);
|
|
294
|
-
const userId = decodeBase64(opened.payload.subarray(0, UserId.keyLength));
|
|
295
|
-
const sessionTag = this.users.get(userId);
|
|
296
|
-
if (!sessionTag)
|
|
297
|
-
throw new Error("Session not found for user: " + userId);
|
|
298
|
-
const session = this.sessions.get(sessionTag);
|
|
299
|
-
if (!session)
|
|
300
|
-
throw new Error("Session not found for sessionTag: " + datagram.sessionTag);
|
|
301
|
-
this.emitter.emit('send', { session, datagram: Datagram.from(opened.payload.slice(UserId.keyLength)), userId: session.userId });
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
case Protocols.BOOTSTRAP: {
|
|
305
|
-
//console.debug("Opening Bootstrap");
|
|
306
|
-
if (!datagram.payload)
|
|
307
|
-
throw new Error("Invalid Bootstrap");
|
|
308
|
-
const keyExchangeData = decodeData(datagram.payload);
|
|
309
|
-
const userId = UserId.fromKey(keyExchangeData.identityKey);
|
|
310
|
-
const request = new BootstrapRequest(userId, keyExchangeData);
|
|
311
|
-
let sended = false;
|
|
312
|
-
request.onChange = (request) => {
|
|
313
|
-
if (request.status === 'accepted' && !sended) {
|
|
314
|
-
sended = true;
|
|
315
|
-
this.sendHandshake(request.data);
|
|
316
|
-
}
|
|
317
|
-
};
|
|
318
|
-
this.bootstraps.set(userId.toString(), request);
|
|
319
|
-
this.emitter.emit('bootstrap', { request });
|
|
320
|
-
return;
|
|
264
|
+
if (!compareBytes(payload, crypto.ECDH.scalarMult(this.privateIdentityKey.exchangeKey, session.identityKey.exchangeKey)))
|
|
265
|
+
throw new Error("Error validating handshake data");
|
|
266
|
+
return 'ack';
|
|
321
267
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
268
|
+
//console.debug("Opening Handshake Syn");
|
|
269
|
+
const data = decodeData(encrypted.payload);
|
|
270
|
+
if (!encrypted.verify(IdentityKey.from(data.identityKey).signatureKey))
|
|
271
|
+
throw new Error("Signature not verified");
|
|
272
|
+
const { session, associatedData } = this.keyExchange.digestMessage(data);
|
|
273
|
+
this.sessions.set(session.sessionTag, session);
|
|
274
|
+
this.users.set(session.userId.toString(), session.sessionTag);
|
|
275
|
+
this.bundles.set(session.userId.toString(), decodeData(associatedData));
|
|
276
|
+
return 'syn';
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
this.error(error);
|
|
280
|
+
throw error;
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
open(datagram) {
|
|
285
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
286
|
+
try {
|
|
287
|
+
if (datagram instanceof Uint8Array)
|
|
288
|
+
datagram = Datagram.from(datagram);
|
|
289
|
+
switch (datagram.protocol) {
|
|
290
|
+
case Protocols.HANDSHAKE: {
|
|
291
|
+
const encrypted = EncryptedDatagram.from(datagram);
|
|
292
|
+
if (!encrypted.payload)
|
|
293
|
+
throw new Error("Missing payload");
|
|
294
|
+
const handshakeState = yield this.openHandshake(datagram);
|
|
295
|
+
const session = this.sessions.get(encrypted.sessionTag);
|
|
296
|
+
if (!session)
|
|
297
|
+
throw new Error("Session not found for sessionTag: " + encrypted.sessionTag);
|
|
298
|
+
if (handshakeState === 'syn')
|
|
299
|
+
this.sendHandshake(session);
|
|
300
|
+
this.emitter.emit('handshake', { session });
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
case Protocols.MESSAGE:
|
|
304
|
+
//console.debug("Opening Message");
|
|
305
|
+
this.emitter.emit('message', yield this.decrypt(datagram));
|
|
306
|
+
return;
|
|
307
|
+
case Protocols.RELAY: {
|
|
308
|
+
//console.debug("Opening Relay");
|
|
309
|
+
const opened = yield this.decrypt(datagram);
|
|
310
|
+
const userId = decodeBase64(opened.payload.subarray(0, UserId.keyLength));
|
|
311
|
+
const sessionTag = this.users.get(userId);
|
|
312
|
+
if (!sessionTag)
|
|
313
|
+
throw new Error("Session not found for user: " + userId);
|
|
314
|
+
const session = this.sessions.get(sessionTag);
|
|
315
|
+
if (!session)
|
|
316
|
+
throw new Error("Session not found for sessionTag: " + datagram.sessionTag);
|
|
317
|
+
this.emitter.emit('send', { session, datagram: Datagram.from(opened.payload.slice(UserId.keyLength)), userId: session.userId });
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
case Protocols.BOOTSTRAP: {
|
|
321
|
+
//console.debug("Opening Bootstrap");
|
|
322
|
+
if (!datagram.payload)
|
|
323
|
+
throw new Error("Invalid Bootstrap");
|
|
324
|
+
const keyExchangeData = decodeData(datagram.payload);
|
|
325
|
+
const userId = UserId.fromKey(keyExchangeData.identityKey);
|
|
326
|
+
const request = new BootstrapRequest(userId, keyExchangeData);
|
|
327
|
+
let sended = false;
|
|
328
|
+
request.onChange = (request) => {
|
|
329
|
+
if (request.status === 'accepted' && !sended) {
|
|
330
|
+
sended = true;
|
|
331
|
+
this.sendHandshake(request.data);
|
|
339
332
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
333
|
+
};
|
|
334
|
+
this.bootstraps.set(userId.toString(), request);
|
|
335
|
+
this.emitter.emit('bootstrap', { request });
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
/*case Protocols.DISCOVER: {
|
|
339
|
+
//console.debug("Opening Discover");
|
|
340
|
+
const { session, payload } = await this.decrypt(datagram);
|
|
341
|
+
const message = decodeData<DiscoverMessage>(payload);
|
|
342
|
+
if (message.type === DiscoverType.REQUEST && message.discoverId && !(await this.sessions.has(message.discoverId))) {
|
|
343
|
+
let data: KeyExchangeData;
|
|
344
|
+
if (message.discoverId === this.userId.toString()) {
|
|
345
|
+
data = await this.keyExchange.generateData();
|
|
346
|
+
} else {
|
|
347
|
+
const bundle = await this.bundles.get(message.discoverId);
|
|
348
|
+
if (!bundle)
|
|
349
|
+
return;
|
|
350
|
+
const { version, identityKey, signedPreKey, signature } = bundle;
|
|
351
|
+
const onetimePreKey = bundle.onetimePreKeys.shift();
|
|
352
|
+
if (!onetimePreKey) {
|
|
353
|
+
await this.bundles.delete(message.discoverId);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
data = {
|
|
357
|
+
version,
|
|
358
|
+
identityKey,
|
|
359
|
+
signedPreKey,
|
|
360
|
+
signature,
|
|
361
|
+
onetimePreKey
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
const response: DiscoverMessage = { type: DiscoverType.RESPONSE, discoverId: message.discoverId, data };
|
|
365
|
+
this.emitter.emit('send', await this.encrypt(session.userId, Protocols.DISCOVER, encodeData(response)));
|
|
366
|
+
} else if (message.type === DiscoverType.RESPONSE && this.discovers.has(message.discoverId)) {
|
|
367
|
+
this.discovers.delete(message.discoverId);
|
|
368
|
+
if (message.data)
|
|
369
|
+
await this.sendHandshake(message.data);
|
|
347
370
|
}
|
|
348
|
-
|
|
349
|
-
this.emitter.emit('send', await this.encrypt(session.userId, Protocols.DISCOVER, encodeData(response)));
|
|
350
|
-
} else if (message.type === DiscoverType.RESPONSE && this.discovers.has(message.discoverId)) {
|
|
351
|
-
this.discovers.delete(message.discoverId);
|
|
352
|
-
if (message.data)
|
|
353
|
-
await this.sendHandshake(message.data);
|
|
371
|
+
return;
|
|
354
372
|
}
|
|
355
|
-
|
|
373
|
+
|
|
374
|
+
case Protocols.PING:
|
|
375
|
+
datagram = EncryptedDatagram.from(datagram);
|
|
376
|
+
const session = await this.sessions.get(datagram.sessionTag!);
|
|
377
|
+
if (!session)
|
|
378
|
+
throw new Error("Session not found for sessionTag: " + datagram.sessionTag);
|
|
379
|
+
this.emitter.emit('ping', { session });
|
|
380
|
+
return;*/
|
|
381
|
+
default:
|
|
382
|
+
throw new Error("Invalid protocol");
|
|
356
383
|
}
|
|
357
|
-
|
|
358
|
-
case Protocols.PING:
|
|
359
|
-
datagram = EncryptedDatagram.from(datagram);
|
|
360
|
-
const session = await this.sessions.get(datagram.sessionTag!);
|
|
361
|
-
if (!session)
|
|
362
|
-
throw new Error("Session not found for sessionTag: " + datagram.sessionTag);
|
|
363
|
-
this.emitter.emit('ping', { session });
|
|
364
|
-
return;*/
|
|
365
|
-
default:
|
|
366
|
-
throw new Error("Invalid protocol");
|
|
367
384
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
}
|
|
385
|
+
catch (error) {
|
|
386
|
+
this.error(error);
|
|
387
|
+
throw error;
|
|
388
|
+
}
|
|
389
|
+
});
|
|
373
390
|
}
|
|
374
391
|
toJSON() {
|
|
375
392
|
return {
|
|
@@ -377,7 +394,7 @@ export class FreeSignalNode {
|
|
|
377
394
|
sessions: Array.from(this.sessions.entries()).map(([key, session]) => [key, session.toJSON()]),
|
|
378
395
|
users: Array.from(this.users.entries()),
|
|
379
396
|
bundles: Array.from(this.bundles),
|
|
380
|
-
keyExchange: this.keyExchange.toJSON(),
|
|
397
|
+
keyExchange: this.keyExchange.toJSON().storage,
|
|
381
398
|
};
|
|
382
399
|
}
|
|
383
400
|
}
|
package/dist/x3dh.d.ts
CHANGED
|
@@ -29,10 +29,7 @@ export declare class KeyExchange {
|
|
|
29
29
|
private static readonly maxOPK;
|
|
30
30
|
private readonly privateIdentityKey;
|
|
31
31
|
private readonly storage;
|
|
32
|
-
constructor({ storage, privateIdentityKey }
|
|
33
|
-
storage?: Array<[string, Crypto.KeyPair]>;
|
|
34
|
-
privateIdentityKey?: PrivateIdentityKey;
|
|
35
|
-
});
|
|
32
|
+
constructor({ storage, privateIdentityKey }?: Partial<KeyExchangeState>);
|
|
36
33
|
get identityKey(): IdentityKey;
|
|
37
34
|
private generateSPK;
|
|
38
35
|
private generateOPK;
|
package/dist/x3dh.js
CHANGED
|
@@ -22,7 +22,7 @@ import { concatBytes, decodeBase64, encodeBase64, compareBytes } from "@freesign
|
|
|
22
22
|
import { decryptData, encryptData, IdentityKey } from "./types.js";
|
|
23
23
|
import { createIdentity } from "./index.js";
|
|
24
24
|
export class KeyExchange {
|
|
25
|
-
constructor({ storage, privateIdentityKey }) {
|
|
25
|
+
constructor({ storage, privateIdentityKey } = {}) {
|
|
26
26
|
this.storage = new Map(storage);
|
|
27
27
|
this.privateIdentityKey = privateIdentityKey !== null && privateIdentityKey !== void 0 ? privateIdentityKey : createIdentity();
|
|
28
28
|
}
|