@freesignal/protocol 0.7.0 → 0.7.3
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 -0
- package/dist/double-ratchet.js +2 -1
- package/dist/index.d.ts +1 -0
- package/dist/node.d.ts +23 -14
- package/dist/node.js +105 -79
- package/dist/types.d.ts +13 -29
- package/dist/types.js +31 -65
- package/package.json +45 -46
package/dist/double-ratchet.d.ts
CHANGED
package/dist/double-ratchet.js
CHANGED
|
@@ -34,8 +34,9 @@ class KeySession {
|
|
|
34
34
|
this.headerKeys = new Map();
|
|
35
35
|
this.previousKeys = new KeyMap();
|
|
36
36
|
this.identityKey = identityKey;
|
|
37
|
-
this.keyPair = crypto_1.default.ECDH.keyPair(secretKey);
|
|
38
37
|
this.rootKey = rootKey;
|
|
38
|
+
this.sessionTag = (0, utils_1.decodeBase64)(crypto_1.default.hkdf(rootKey, new Uint8Array(32).fill(0), "/freesignal/session-authtag", 32));
|
|
39
|
+
this.keyPair = crypto_1.default.ECDH.keyPair(secretKey);
|
|
39
40
|
if (headerKey)
|
|
40
41
|
this.headerKey = headerKey;
|
|
41
42
|
if (nextHeaderKey) {
|
package/dist/index.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export declare function createIdentity(seed?: Uint8Array): PrivateIdentityKey;
|
|
|
30
30
|
/** */
|
|
31
31
|
export declare function createNode(storage: Database<{
|
|
32
32
|
sessions: LocalStorage<string, ExportedKeySession>;
|
|
33
|
+
users: LocalStorage<string, string>;
|
|
33
34
|
keyExchange: LocalStorage<string, Crypto.KeyPair>;
|
|
34
35
|
bundles: LocalStorage<string, KeyExchangeDataBundle>;
|
|
35
36
|
bootstraps: LocalStorage<string, BootstrapRequest>;
|
package/dist/node.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 { Database, LocalStorage, Crypto, KeyExchangeDataBundle, KeyExchangeData } from "@freesignal/interfaces";
|
|
20
|
-
import { Datagram,
|
|
20
|
+
import { Datagram, EncryptedDatagram, IdentityKey, PrivateIdentityKey, Protocols, UserId } from "./types";
|
|
21
21
|
import { KeyExchange } from "./x3dh";
|
|
22
22
|
import { ExportedKeySession, KeySession } from "./double-ratchet";
|
|
23
23
|
import EventEmitter, { EventCall } from "easyemitter.ts";
|
|
@@ -33,48 +33,57 @@ export declare class BootstrapRequest extends EventEmitter<'change', BootstrapRe
|
|
|
33
33
|
deny(): void;
|
|
34
34
|
}
|
|
35
35
|
type NodeEventData = {
|
|
36
|
-
|
|
36
|
+
session?: KeySession;
|
|
37
37
|
payload?: Uint8Array;
|
|
38
38
|
datagram?: Datagram;
|
|
39
|
+
request?: BootstrapRequest;
|
|
40
|
+
};
|
|
41
|
+
type SendEventData = {
|
|
42
|
+
session?: KeySession;
|
|
43
|
+
datagram: Datagram;
|
|
39
44
|
};
|
|
40
45
|
type MessageEventData = {
|
|
41
|
-
|
|
46
|
+
session: KeySession;
|
|
42
47
|
payload: Uint8Array;
|
|
43
48
|
};
|
|
44
49
|
export declare class FreeSignalNode {
|
|
45
50
|
protected readonly privateIdentityKey: PrivateIdentityKey;
|
|
46
51
|
protected readonly sessions: SessionMap;
|
|
52
|
+
protected readonly users: LocalStorage<string, string>;
|
|
47
53
|
protected readonly bundles: LocalStorage<string, KeyExchangeDataBundle>;
|
|
48
54
|
protected readonly keyExchange: KeyExchange;
|
|
49
55
|
protected readonly discovers: Set<string>;
|
|
50
56
|
protected readonly bootstraps: LocalStorage<string, BootstrapRequest>;
|
|
51
|
-
protected readonly emitter: EventEmitter<"message" | "send" | "
|
|
57
|
+
protected readonly emitter: EventEmitter<"message" | "send" | "handshake" | "ping" | "bootstrap", NodeEventData>;
|
|
52
58
|
constructor(storage: Database<{
|
|
53
59
|
sessions: LocalStorage<string, ExportedKeySession>;
|
|
60
|
+
users: LocalStorage<string, string>;
|
|
54
61
|
keyExchange: LocalStorage<string, Crypto.KeyPair>;
|
|
55
62
|
bundles: LocalStorage<string, KeyExchangeDataBundle>;
|
|
56
63
|
bootstraps: LocalStorage<string, BootstrapRequest>;
|
|
57
64
|
}>, privateIdentityKey?: PrivateIdentityKey);
|
|
65
|
+
protected messageHandler: EventCall<"message", NodeEventData>;
|
|
66
|
+
protected sendHandler: EventCall<"send", NodeEventData>;
|
|
67
|
+
protected handshakeHandler: EventCall<"handshake", NodeEventData>;
|
|
68
|
+
protected bootstrapHandler: EventCall<"bootstrap", NodeEventData>;
|
|
58
69
|
onMessage: (data: MessageEventData) => void;
|
|
59
70
|
onSend: (data: Uint8Array) => void;
|
|
60
|
-
|
|
71
|
+
onHandshake: (userId: UserId) => void;
|
|
72
|
+
onRequest: (request: BootstrapRequest) => void;
|
|
73
|
+
getRequest(userId: string): Promise<BootstrapRequest | undefined>;
|
|
61
74
|
waitHandshaked(userId: UserId | string, timeout?: number): Promise<void>;
|
|
62
75
|
get identityKey(): IdentityKey;
|
|
63
76
|
get userId(): UserId;
|
|
64
|
-
|
|
65
|
-
onRequest: (request: BootstrapRequest) => void;
|
|
66
|
-
getRequest: (userId: string) => Promise<BootstrapRequest | undefined>;
|
|
67
|
-
};
|
|
68
|
-
protected encrypt(receiverId: string | UserId, protocol: Protocols, data: Uint8Array): Promise<Datagram>;
|
|
77
|
+
protected encrypt(receiverId: string | UserId, protocol: Protocols, data: Uint8Array): Promise<SendEventData>;
|
|
69
78
|
sendHandshake(data: KeyExchangeData): Promise<void>;
|
|
70
|
-
sendHandshake(
|
|
79
|
+
sendHandshake(session: KeySession): Promise<void>;
|
|
71
80
|
sendData<T>(receiverId: string | UserId, data: T): Promise<void>;
|
|
72
|
-
sendRelay(receiverId: string | UserId, data: Datagram): Promise<void>;
|
|
81
|
+
sendRelay(relayId: string | UserId, receiverId: string | UserId, data: Datagram): Promise<void>;
|
|
73
82
|
sendPing(receiverId: string | UserId): Promise<void>;
|
|
74
83
|
sendDiscover(receiverId: string | UserId, discoverId: string | UserId): Promise<void>;
|
|
75
84
|
sendBootstrap(receiverId: string | UserId): Promise<void>;
|
|
76
|
-
protected decrypt(datagram: Datagram): Promise<
|
|
77
|
-
protected open(datagram: Datagram | Uint8Array): Promise<void>;
|
|
85
|
+
protected decrypt(datagram: EncryptedDatagram | Datagram | Uint8Array): Promise<MessageEventData>;
|
|
86
|
+
protected open(datagram: Datagram | EncryptedDatagram | Uint8Array): Promise<void>;
|
|
78
87
|
}
|
|
79
88
|
declare class SessionMap implements LocalStorage<string, KeySession> {
|
|
80
89
|
readonly storage: LocalStorage<string, ExportedKeySession>;
|
package/dist/node.js
CHANGED
|
@@ -82,29 +82,34 @@ class FreeSignalNode {
|
|
|
82
82
|
constructor(storage, privateIdentityKey) {
|
|
83
83
|
this.discovers = new Set();
|
|
84
84
|
this.emitter = new easyemitter_ts_1.default();
|
|
85
|
+
this.messageHandler = (data) => { var _a, _b; return this.onMessage({ session: (_a = data.data) === null || _a === void 0 ? void 0 : _a.session, payload: (_b = data.data) === null || _b === void 0 ? void 0 : _b.payload }); };
|
|
86
|
+
this.sendHandler = (data) => this.onSend(data.data.datagram.toBytes());
|
|
87
|
+
this.handshakeHandler = (data) => { var _a, _b; return this.onHandshake(types_1.UserId.from((_b = (_a = data.data) === null || _a === void 0 ? void 0 : _a.session) === null || _b === void 0 ? void 0 : _b.userId)); };
|
|
88
|
+
this.bootstrapHandler = (data) => { var _a; return this.onRequest((_a = data.data) === null || _a === void 0 ? void 0 : _a.request); };
|
|
85
89
|
this.onMessage = () => { };
|
|
86
90
|
this.onSend = () => { };
|
|
87
|
-
this.
|
|
88
|
-
this.
|
|
89
|
-
onRequest: () => { },
|
|
90
|
-
getRequest: (userId) => {
|
|
91
|
-
return this.bootstraps.get(userId);
|
|
92
|
-
}
|
|
93
|
-
};
|
|
91
|
+
this.onHandshake = () => { };
|
|
92
|
+
this.onRequest = () => { };
|
|
94
93
|
this.privateIdentityKey = privateIdentityKey !== null && privateIdentityKey !== void 0 ? privateIdentityKey : (0, _1.createIdentity)();
|
|
95
94
|
this.sessions = new SessionMap(storage.sessions);
|
|
95
|
+
this.users = storage.users;
|
|
96
96
|
this.keyExchange = new x3dh_1.KeyExchange(storage.keyExchange, this.privateIdentityKey);
|
|
97
97
|
this.bundles = storage.bundles;
|
|
98
98
|
this.bootstraps = storage.bootstraps;
|
|
99
|
-
this.emitter.on('
|
|
100
|
-
this.emitter.on('
|
|
99
|
+
this.emitter.on('message', this.messageHandler);
|
|
100
|
+
this.emitter.on('send', this.sendHandler);
|
|
101
|
+
this.emitter.on('handshake', this.handshakeHandler);
|
|
102
|
+
this.emitter.on('bootstrap', this.bootstrapHandler);
|
|
103
|
+
}
|
|
104
|
+
getRequest(userId) {
|
|
105
|
+
return this.bootstraps.get(userId);
|
|
101
106
|
}
|
|
102
107
|
waitHandshaked(userId, timeout) {
|
|
103
108
|
return __awaiter(this, void 0, void 0, function* () {
|
|
104
|
-
var _a;
|
|
109
|
+
var _a, _b;
|
|
105
110
|
if (timeout)
|
|
106
111
|
setTimeout(() => { throw new Error(); }, timeout);
|
|
107
|
-
while (((_a = (yield this.emitter.wait('
|
|
112
|
+
while (((_b = (_a = (yield this.emitter.wait('handshake', timeout))) === null || _a === void 0 ? void 0 : _a.session) === null || _b === void 0 ? void 0 : _b.userId.toString()) !== userId.toString())
|
|
108
113
|
;
|
|
109
114
|
});
|
|
110
115
|
}
|
|
@@ -116,55 +121,58 @@ class FreeSignalNode {
|
|
|
116
121
|
}
|
|
117
122
|
encrypt(receiverId, protocol, data) {
|
|
118
123
|
return __awaiter(this, void 0, void 0, function* () {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
124
|
+
const sessionTag = yield this.users.get(receiverId.toString());
|
|
125
|
+
if (!sessionTag)
|
|
126
|
+
throw new Error("User not found: " + receiverId);
|
|
127
|
+
const session = yield this.sessions.get(sessionTag);
|
|
122
128
|
if (!session)
|
|
123
|
-
throw new Error("Session not found for
|
|
129
|
+
throw new Error("Session not found for sessionTag: " + sessionTag);
|
|
124
130
|
const encrypted = (0, types_1.encryptData)(session, data);
|
|
125
|
-
this.sessions.set(receiverId, session);
|
|
126
|
-
return new types_1.
|
|
131
|
+
this.sessions.set(receiverId.toString(), session);
|
|
132
|
+
return { session, datagram: new types_1.EncryptedDatagram(protocol, session.sessionTag, encrypted).sign(this.privateIdentityKey.signatureKey) };
|
|
127
133
|
});
|
|
128
134
|
}
|
|
129
135
|
sendHandshake(data) {
|
|
130
136
|
return __awaiter(this, void 0, void 0, function* () {
|
|
131
|
-
|
|
132
|
-
if (typeof data === 'string' || data instanceof types_1.UserId) {
|
|
137
|
+
if (data instanceof double_ratchet_1.KeySession) {
|
|
133
138
|
//console.debug("Sending Handshake Ack");
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const datagram = yield this.encrypt(userId, types_1.Protocols.HANDSHAKE, crypto_1.default.ECDH.scalarMult(this.privateIdentityKey.exchangeKey, identityKey.exchangeKey));
|
|
139
|
-
this.emitter.emit('send', { header: datagram.header, datagram });
|
|
139
|
+
const session = yield this.sessions.get(data.sessionTag);
|
|
140
|
+
if (!session)
|
|
141
|
+
throw new Error("Session not found for user: " + data);
|
|
142
|
+
this.emitter.emit('send', yield this.encrypt(session.userId, types_1.Protocols.HANDSHAKE, crypto_1.default.ECDH.scalarMult(this.privateIdentityKey.exchangeKey, session.identityKey.exchangeKey)));
|
|
140
143
|
return;
|
|
141
144
|
}
|
|
142
145
|
//console.debug("Sending Handshake Syn");
|
|
143
146
|
const { session, message } = yield this.keyExchange.digestData(data, (0, utils_1.encodeData)(yield this.keyExchange.generateBundle()));
|
|
144
|
-
yield this.sessions.set(session.
|
|
145
|
-
|
|
146
|
-
|
|
147
|
+
yield this.sessions.set(session.sessionTag, session);
|
|
148
|
+
yield this.users.set(session.userId.toString(), session.sessionTag);
|
|
149
|
+
const datagram = new types_1.Datagram(types_1.Protocols.HANDSHAKE, (0, utils_1.encodeData)(message), session.sessionTag).sign(this.privateIdentityKey.signatureKey);
|
|
150
|
+
this.emitter.emit('send', { session, datagram });
|
|
147
151
|
});
|
|
148
152
|
}
|
|
149
153
|
sendData(receiverId, data) {
|
|
150
154
|
return __awaiter(this, void 0, void 0, function* () {
|
|
151
155
|
//console.debug("Sending Data");
|
|
152
|
-
|
|
153
|
-
this.emitter.emit('send', { header: datagram.header, datagram });
|
|
156
|
+
this.emitter.emit('send', yield this.encrypt(receiverId, types_1.Protocols.MESSAGE, (0, utils_1.encodeData)(data)));
|
|
154
157
|
});
|
|
155
158
|
}
|
|
156
|
-
sendRelay(receiverId, data) {
|
|
159
|
+
sendRelay(relayId, receiverId, data) {
|
|
157
160
|
return __awaiter(this, void 0, void 0, function* () {
|
|
158
161
|
//console.debug("Sending Relay");
|
|
159
|
-
|
|
160
|
-
this.emitter.emit('send', { header: datagram.header, datagram });
|
|
162
|
+
this.emitter.emit('send', yield this.encrypt(relayId, types_1.Protocols.RELAY, (0, utils_1.concatBytes)(types_1.UserId.from(receiverId).toBytes(), data.toBytes())));
|
|
161
163
|
});
|
|
162
164
|
}
|
|
163
165
|
sendPing(receiverId) {
|
|
164
166
|
return __awaiter(this, void 0, void 0, function* () {
|
|
165
167
|
//console.debug("Sending Ping");
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
+
const sessionTag = yield this.users.get(receiverId.toString());
|
|
169
|
+
if (!sessionTag)
|
|
170
|
+
throw new Error("Session not found for user: " + receiverId);
|
|
171
|
+
const session = yield this.sessions.get(sessionTag);
|
|
172
|
+
if (!session)
|
|
173
|
+
throw new Error("Session not found for sessionTag: " + sessionTag);
|
|
174
|
+
const datagram = new types_1.Datagram(types_1.Protocols.PING, undefined, session.sessionTag);
|
|
175
|
+
this.emitter.emit('send', { session, datagram });
|
|
168
176
|
});
|
|
169
177
|
}
|
|
170
178
|
sendDiscover(receiverId, discoverId) {
|
|
@@ -179,76 +187,88 @@ class FreeSignalNode {
|
|
|
179
187
|
discoverId
|
|
180
188
|
};
|
|
181
189
|
this.discovers.add(receiverId);
|
|
182
|
-
|
|
183
|
-
this.emitter.emit('send', { header: datagram.header, datagram });
|
|
190
|
+
this.emitter.emit('send', yield this.encrypt(receiverId, types_1.Protocols.DISCOVER, (0, utils_1.encodeData)(message)));
|
|
184
191
|
});
|
|
185
192
|
}
|
|
186
193
|
sendBootstrap(receiverId) {
|
|
187
194
|
return __awaiter(this, void 0, void 0, function* () {
|
|
188
195
|
//console.debug("Sending Bootstrap");
|
|
189
|
-
|
|
190
|
-
|
|
196
|
+
if (yield this.sessions.has(receiverId.toString()))
|
|
197
|
+
throw new Error("Session exists");
|
|
198
|
+
const datagram = new types_1.Datagram(types_1.Protocols.BOOTSTRAP, (0, utils_1.encodeData)(yield this.keyExchange.generateData()));
|
|
199
|
+
this.emitter.emit('send', { datagram });
|
|
191
200
|
});
|
|
192
201
|
}
|
|
193
202
|
decrypt(datagram) {
|
|
194
203
|
return __awaiter(this, void 0, void 0, function* () {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
if (!types_1.Datagram.verify(datagram, identityKey.signatureKey))
|
|
200
|
-
throw new Error("Signature not verified");
|
|
201
|
-
const session = yield this.sessions.get(datagram.sender);
|
|
204
|
+
datagram = types_1.EncryptedDatagram.from(datagram);
|
|
205
|
+
if (!datagram.sessionTag)
|
|
206
|
+
throw new Error("Datagram not encrypted");
|
|
207
|
+
const session = yield this.sessions.get(datagram.sessionTag);
|
|
202
208
|
if (!session)
|
|
203
|
-
throw new Error("Session not found for
|
|
209
|
+
throw new Error("Session not found for sessionTag: " + datagram.sessionTag);
|
|
210
|
+
if (!datagram.verify(session.identityKey.signatureKey))
|
|
211
|
+
throw new Error("Signature not verified");
|
|
204
212
|
if (!datagram.payload)
|
|
205
213
|
throw new Error("Missing payload");
|
|
206
214
|
const decrypted = (0, types_1.decryptData)(session, datagram.payload);
|
|
207
|
-
this.sessions.set(datagram.
|
|
208
|
-
return decrypted;
|
|
215
|
+
this.sessions.set(datagram.sessionTag, session);
|
|
216
|
+
return { session, payload: decrypted };
|
|
209
217
|
});
|
|
210
218
|
}
|
|
211
219
|
open(datagram) {
|
|
212
220
|
return __awaiter(this, void 0, void 0, function* () {
|
|
213
|
-
var _a;
|
|
214
221
|
if (datagram instanceof Uint8Array)
|
|
215
222
|
datagram = types_1.Datagram.from(datagram);
|
|
216
223
|
switch (datagram.protocol) {
|
|
217
|
-
case types_1.Protocols.HANDSHAKE:
|
|
218
|
-
|
|
224
|
+
case types_1.Protocols.HANDSHAKE: {
|
|
225
|
+
const encrypted = types_1.EncryptedDatagram.from(datagram);
|
|
226
|
+
if (!encrypted.payload)
|
|
219
227
|
throw new Error("Missing payload");
|
|
220
|
-
if (yield this.sessions.has(
|
|
228
|
+
if (yield this.sessions.has(encrypted.sessionTag)) {
|
|
221
229
|
//console.debug("Opening Handshake Ack");
|
|
222
|
-
const
|
|
223
|
-
const
|
|
224
|
-
if (!
|
|
225
|
-
throw new Error("
|
|
226
|
-
if (!(0, utils_1.compareBytes)(payload, crypto_1.default.ECDH.scalarMult(this.privateIdentityKey.exchangeKey, identityKey.exchangeKey)))
|
|
230
|
+
const session = yield this.sessions.get(encrypted.sessionTag);
|
|
231
|
+
const { payload } = yield this.decrypt(encrypted);
|
|
232
|
+
if (!session)
|
|
233
|
+
throw new Error("Session not found for sessionTag: " + datagram.sessionTag);
|
|
234
|
+
if (!(0, utils_1.compareBytes)(payload, crypto_1.default.ECDH.scalarMult(this.privateIdentityKey.exchangeKey, session.identityKey.exchangeKey)))
|
|
227
235
|
throw new Error("Error validating handshake data");
|
|
228
|
-
this.emitter.emit('
|
|
236
|
+
this.emitter.emit('handshake', { session });
|
|
229
237
|
return;
|
|
230
238
|
}
|
|
231
239
|
//console.debug("Opening Handshake Syn");
|
|
232
|
-
const data = (0, utils_1.decodeData)(
|
|
233
|
-
if (!
|
|
240
|
+
const data = (0, utils_1.decodeData)(encrypted.payload);
|
|
241
|
+
if (!datagram.verify(types_1.IdentityKey.from(data.identityKey).signatureKey))
|
|
234
242
|
throw new Error("Signature not verified");
|
|
235
243
|
const { session, associatedData } = yield this.keyExchange.digestMessage(data);
|
|
236
|
-
yield this.sessions.set(session.
|
|
244
|
+
yield this.sessions.set(session.sessionTag, session);
|
|
245
|
+
yield this.users.set(session.userId.toString(), session.sessionTag);
|
|
237
246
|
yield this.bundles.set(session.userId.toString(), (0, utils_1.decodeData)(associatedData));
|
|
238
|
-
yield this.sendHandshake(session
|
|
239
|
-
this.emitter.emit('
|
|
247
|
+
yield this.sendHandshake(session);
|
|
248
|
+
this.emitter.emit('handshake', { session });
|
|
240
249
|
return;
|
|
250
|
+
}
|
|
241
251
|
case types_1.Protocols.MESSAGE:
|
|
242
252
|
//console.debug("Opening Message");
|
|
243
|
-
this.emitter.emit('message',
|
|
253
|
+
this.emitter.emit('message', yield this.decrypt(datagram));
|
|
244
254
|
return;
|
|
245
|
-
case types_1.Protocols.RELAY:
|
|
255
|
+
case types_1.Protocols.RELAY: {
|
|
246
256
|
//console.debug("Opening Relay");
|
|
247
|
-
|
|
257
|
+
const opened = yield this.decrypt(datagram);
|
|
258
|
+
const userId = (0, utils_1.decodeBase64)(opened.payload.subarray(0, types_1.UserId.keyLength));
|
|
259
|
+
const sessionTag = yield this.users.get(userId);
|
|
260
|
+
if (!sessionTag)
|
|
261
|
+
throw new Error("Session not found for user: " + userId);
|
|
262
|
+
const session = yield this.sessions.get(sessionTag);
|
|
263
|
+
if (!session)
|
|
264
|
+
throw new Error("Session not found for sessionTag: " + datagram.sessionTag);
|
|
265
|
+
this.emitter.emit('send', { session, payload: opened.payload.slice(types_1.UserId.keyLength) });
|
|
248
266
|
return;
|
|
249
|
-
|
|
267
|
+
}
|
|
268
|
+
case types_1.Protocols.DISCOVER: {
|
|
250
269
|
//console.debug("Opening Discover");
|
|
251
|
-
const
|
|
270
|
+
const { session, payload } = yield this.decrypt(datagram);
|
|
271
|
+
const message = (0, utils_1.decodeData)(payload);
|
|
252
272
|
if (message.type === types_1.DiscoverType.REQUEST && message.discoverId && !(yield this.sessions.has(message.discoverId))) {
|
|
253
273
|
let data;
|
|
254
274
|
if (message.discoverId === this.userId.toString()) {
|
|
@@ -273,7 +293,7 @@ class FreeSignalNode {
|
|
|
273
293
|
};
|
|
274
294
|
}
|
|
275
295
|
const response = { type: types_1.DiscoverType.RESPONSE, discoverId: message.discoverId, data };
|
|
276
|
-
this.emitter.emit('send', yield this.encrypt(
|
|
296
|
+
this.emitter.emit('send', yield this.encrypt(session.userId, types_1.Protocols.DISCOVER, (0, utils_1.encodeData)(response)));
|
|
277
297
|
}
|
|
278
298
|
else if (message.type === types_1.DiscoverType.RESPONSE && this.discovers.has(message.discoverId)) {
|
|
279
299
|
this.discovers.delete(message.discoverId);
|
|
@@ -281,24 +301,30 @@ class FreeSignalNode {
|
|
|
281
301
|
yield this.sendHandshake(message.data);
|
|
282
302
|
}
|
|
283
303
|
return;
|
|
284
|
-
|
|
304
|
+
}
|
|
305
|
+
case types_1.Protocols.BOOTSTRAP: {
|
|
285
306
|
//console.debug("Opening Bootstrap");
|
|
286
307
|
if (!datagram.payload)
|
|
287
308
|
throw new Error("Invalid Bootstrap");
|
|
288
309
|
const keyExchangeData = (0, utils_1.decodeData)(datagram.payload);
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
310
|
+
const userId = types_1.UserId.fromKey(keyExchangeData.identityKey);
|
|
311
|
+
const request = new BootstrapRequest(userId, keyExchangeData);
|
|
312
|
+
request.onChange = (event) => {
|
|
313
|
+
var _a;
|
|
293
314
|
if (!request.data)
|
|
294
315
|
throw new Error("Error sending handshake");
|
|
295
|
-
this.sendHandshake(
|
|
316
|
+
this.sendHandshake((_a = event.data) === null || _a === void 0 ? void 0 : _a.data);
|
|
296
317
|
};
|
|
297
|
-
yield this.bootstraps.set(
|
|
298
|
-
this.
|
|
318
|
+
yield this.bootstraps.set(userId.toString(), request);
|
|
319
|
+
this.emitter.emit('bootstrap', { request });
|
|
299
320
|
return;
|
|
321
|
+
}
|
|
300
322
|
case types_1.Protocols.PING:
|
|
301
|
-
|
|
323
|
+
datagram = types_1.EncryptedDatagram.from(datagram);
|
|
324
|
+
const session = yield this.sessions.get(datagram.sessionTag);
|
|
325
|
+
if (!session)
|
|
326
|
+
throw new Error("Session not found for sessionTag: " + datagram.sessionTag);
|
|
327
|
+
this.emitter.emit('ping', { session });
|
|
302
328
|
return;
|
|
303
329
|
default:
|
|
304
330
|
throw new Error("Invalid protocol");
|
package/dist/types.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export declare function encryptData(session: KeySession, data: Uint8Array): Encr
|
|
|
22
22
|
export declare function decryptData(session: KeySession, encryptedData: Uint8Array): Uint8Array;
|
|
23
23
|
export declare class UserId implements Encodable {
|
|
24
24
|
private readonly array;
|
|
25
|
+
static readonly keyLength = 32;
|
|
25
26
|
private constructor();
|
|
26
27
|
toString(): string;
|
|
27
28
|
toJSON(): string;
|
|
@@ -86,56 +87,39 @@ export declare namespace Protocols {
|
|
|
86
87
|
function decode(array: Uint8Array): Protocols;
|
|
87
88
|
}
|
|
88
89
|
interface DatagramJSON {
|
|
89
|
-
id: string;
|
|
90
90
|
version: number;
|
|
91
|
-
|
|
92
|
-
receiver: string;
|
|
91
|
+
sessionTag: string | undefined;
|
|
93
92
|
protocol: Protocols;
|
|
94
|
-
createdAt: number;
|
|
95
93
|
payload: string | undefined;
|
|
96
94
|
signature: string | undefined;
|
|
97
95
|
}
|
|
98
96
|
export interface SignedDatagram extends Datagram {
|
|
99
97
|
signature: string;
|
|
100
98
|
}
|
|
101
|
-
export declare class
|
|
102
|
-
static readonly headerLength: number;
|
|
103
|
-
readonly id: string;
|
|
104
|
-
readonly version: number;
|
|
105
|
-
readonly sender: string;
|
|
106
|
-
readonly receiver: string;
|
|
107
|
-
readonly protocol: Protocols;
|
|
108
|
-
readonly createdAt: number;
|
|
109
|
-
private constructor();
|
|
110
|
-
toBytes(): Uint8Array;
|
|
111
|
-
static from(data: Uint8Array | string): DatagramHeader;
|
|
112
|
-
}
|
|
113
|
-
export declare class Datagram implements Encodable, DatagramHeader {
|
|
99
|
+
export declare class Datagram implements Encodable {
|
|
114
100
|
static version: number;
|
|
115
|
-
|
|
101
|
+
static readonly headerLength = 34;
|
|
116
102
|
private _version;
|
|
117
|
-
readonly
|
|
118
|
-
readonly receiver: string;
|
|
103
|
+
readonly sessionTag?: string;
|
|
119
104
|
readonly protocol: Protocols;
|
|
120
|
-
|
|
121
|
-
private _payload?;
|
|
105
|
+
readonly payload?: Uint8Array;
|
|
122
106
|
private _signature?;
|
|
123
|
-
constructor(
|
|
124
|
-
get id(): string;
|
|
107
|
+
constructor(protocol: Protocols, payload?: Uint8Array | Encodable, sessionTag?: Uint8Array | string);
|
|
125
108
|
get version(): number;
|
|
126
|
-
get createdAt(): number;
|
|
127
|
-
set payload(data: Uint8Array);
|
|
128
|
-
get payload(): Uint8Array | undefined;
|
|
129
109
|
get signature(): string | undefined;
|
|
130
110
|
private get unsigned();
|
|
131
|
-
get header(): DatagramHeader;
|
|
132
111
|
toBytes(): Uint8Array;
|
|
133
112
|
sign(secretKey: Uint8Array): SignedDatagram;
|
|
113
|
+
verify(publicKey: Uint8Array): boolean;
|
|
134
114
|
toString(): string;
|
|
135
115
|
toJSON(): DatagramJSON;
|
|
136
|
-
static verify(datagram: Datagram, publicKey: Uint8Array): boolean;
|
|
137
116
|
static from(data: Uint8Array | Datagram | string): Datagram;
|
|
138
117
|
}
|
|
118
|
+
export declare class EncryptedDatagram extends Datagram {
|
|
119
|
+
readonly sessionTag: string;
|
|
120
|
+
constructor(protocol: Protocols, sessionTag: Uint8Array | string, payload?: Uint8Array | Encodable);
|
|
121
|
+
static from(data: Uint8Array | Datagram | string): EncryptedDatagram;
|
|
122
|
+
}
|
|
139
123
|
export declare class EncryptionHeader implements EncryptionKeys, Encodable {
|
|
140
124
|
readonly nonce: Uint8Array;
|
|
141
125
|
static readonly keyLength: number;
|
package/dist/types.js
CHANGED
|
@@ -30,7 +30,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
30
30
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
31
31
|
};
|
|
32
32
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
|
-
exports.AsyncMap = exports.EncryptedData = exports.EncryptionHeader = exports.
|
|
33
|
+
exports.AsyncMap = exports.EncryptedData = exports.EncryptionHeader = exports.EncryptedDatagram = exports.Datagram = exports.Protocols = exports.DiscoverType = exports.PrivateIdentityKey = exports.IdentityKey = exports.UserId = void 0;
|
|
34
34
|
exports.encryptData = encryptData;
|
|
35
35
|
exports.decryptData = decryptData;
|
|
36
36
|
const utils_1 = require("@freesignal/utils");
|
|
@@ -90,7 +90,7 @@ class UserId {
|
|
|
90
90
|
identityKey = (0, utils_1.encodeBase64)(identityKey);
|
|
91
91
|
else if (identityKey instanceof IdentityKey)
|
|
92
92
|
identityKey = (identityKey).toBytes();
|
|
93
|
-
return new UserId(crypto_1.default.hkdf(identityKey, new Uint8Array(32).fill(0), "/freesignal/userid"));
|
|
93
|
+
return new UserId(crypto_1.default.hkdf(identityKey, new Uint8Array(32).fill(0), "/freesignal/userid", UserId.keyLength));
|
|
94
94
|
}
|
|
95
95
|
static from(userId) {
|
|
96
96
|
if (typeof userId === 'string')
|
|
@@ -99,6 +99,7 @@ class UserId {
|
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
exports.UserId = UserId;
|
|
102
|
+
UserId.keyLength = 32;
|
|
102
103
|
class IdentityKey {
|
|
103
104
|
constructor(identityKey) {
|
|
104
105
|
if (identityKey instanceof IdentityKey) {
|
|
@@ -231,52 +232,16 @@ var Protocols;
|
|
|
231
232
|
Protocols.decode = decode;
|
|
232
233
|
})(Protocols || (exports.Protocols = Protocols = {}));
|
|
233
234
|
;
|
|
234
|
-
class DatagramHeader {
|
|
235
|
-
constructor(data) {
|
|
236
|
-
this.version = data[0] & 127;
|
|
237
|
-
this.protocol = Protocols.decode(data.subarray(1, 2));
|
|
238
|
-
this.id = crypto_1.default.UUID.stringify(data.subarray(2, 18));
|
|
239
|
-
this.createdAt = (0, utils_1.bytesToNumber)(data.subarray(18, 26));
|
|
240
|
-
this.sender = (0, utils_1.decodeBase64)(data.subarray(26, 26 + crypto_1.default.EdDSA.publicKeyLength));
|
|
241
|
-
this.receiver = (0, utils_1.decodeBase64)(data.subarray(26 + crypto_1.default.EdDSA.publicKeyLength, DatagramHeader.headerLength));
|
|
242
|
-
}
|
|
243
|
-
toBytes() {
|
|
244
|
-
return (0, utils_1.concatBytes)((0, utils_1.numberToBytes)(this.version, 1), Protocols.encode(this.protocol, 1), crypto_1.default.UUID.parse(this.id), (0, utils_1.numberToBytes)(this.createdAt, 8), (0, utils_1.encodeBase64)(this.sender), (0, utils_1.encodeBase64)(this.receiver));
|
|
245
|
-
}
|
|
246
|
-
static from(data) {
|
|
247
|
-
if (typeof data === 'string')
|
|
248
|
-
data = (0, utils_1.encodeBase64)(data);
|
|
249
|
-
return new DatagramHeader(data);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
exports.DatagramHeader = DatagramHeader;
|
|
253
|
-
DatagramHeader.headerLength = 26 + crypto_1.default.EdDSA.publicKeyLength * 2;
|
|
254
235
|
class Datagram {
|
|
255
|
-
constructor(
|
|
256
|
-
this._id = crypto_1.default.UUID.generate().toString();
|
|
236
|
+
constructor(protocol, payload, sessionTag) {
|
|
257
237
|
this._version = Datagram.version;
|
|
258
|
-
this.
|
|
259
|
-
this.receiver = typeof receiver === 'string' ? receiver : (0, utils_1.decodeBase64)(receiver);
|
|
238
|
+
this.sessionTag = sessionTag instanceof Uint8Array ? (0, utils_1.decodeBase64)(sessionTag) : sessionTag;
|
|
260
239
|
this.protocol = protocol;
|
|
261
|
-
this.
|
|
262
|
-
this._payload = payload instanceof Uint8Array ? payload : payload === null || payload === void 0 ? void 0 : payload.toBytes();
|
|
263
|
-
}
|
|
264
|
-
get id() {
|
|
265
|
-
return this._id;
|
|
240
|
+
this.payload = payload instanceof Uint8Array ? payload : payload === null || payload === void 0 ? void 0 : payload.toBytes();
|
|
266
241
|
}
|
|
267
242
|
get version() {
|
|
268
243
|
return this._version;
|
|
269
244
|
}
|
|
270
|
-
get createdAt() {
|
|
271
|
-
return this._createdAt;
|
|
272
|
-
}
|
|
273
|
-
set payload(data) {
|
|
274
|
-
this._signature = undefined;
|
|
275
|
-
this._payload = data;
|
|
276
|
-
}
|
|
277
|
-
get payload() {
|
|
278
|
-
return this._payload;
|
|
279
|
-
}
|
|
280
245
|
get signature() {
|
|
281
246
|
return this._signature ? (0, utils_1.decodeBase64)(this._signature) : undefined;
|
|
282
247
|
}
|
|
@@ -285,60 +250,48 @@ class Datagram {
|
|
|
285
250
|
data[0] &= 127;
|
|
286
251
|
return data.subarray(0, data.length - (this._signature ? crypto_1.default.EdDSA.signatureLength : 0));
|
|
287
252
|
}
|
|
288
|
-
get header() {
|
|
289
|
-
return DatagramHeader.from(this.toBytes().slice(0, DatagramHeader.headerLength));
|
|
290
|
-
}
|
|
291
253
|
toBytes() {
|
|
292
|
-
var _a, _b
|
|
254
|
+
var _a, _b;
|
|
293
255
|
return (0, utils_1.concatBytes)(new Uint8Array(1).fill(this.version | (this.signature ? 128 : 0)), //1
|
|
294
256
|
Protocols.encode(this.protocol), //1
|
|
295
|
-
(
|
|
296
|
-
new Uint8Array(
|
|
297
|
-
(0, utils_1.encodeBase64)(this.sender), //32
|
|
298
|
-
(0, utils_1.encodeBase64)(this.receiver), //32
|
|
299
|
-
(_b = this._payload) !== null && _b !== void 0 ? _b : new Uint8Array(), (_c = this._signature) !== null && _c !== void 0 ? _c : new Uint8Array());
|
|
257
|
+
this.sessionTag ? (0, utils_1.encodeBase64)(this.sessionTag) : new Uint8Array(32).fill(0), //32
|
|
258
|
+
(_a = this.payload) !== null && _a !== void 0 ? _a : new Uint8Array(), (_b = this._signature) !== null && _b !== void 0 ? _b : new Uint8Array());
|
|
300
259
|
}
|
|
301
260
|
sign(secretKey) {
|
|
302
261
|
this._signature = crypto_1.default.EdDSA.sign(this.unsigned, secretKey);
|
|
303
262
|
return this;
|
|
304
263
|
}
|
|
264
|
+
verify(publicKey) {
|
|
265
|
+
if (!this._signature)
|
|
266
|
+
throw new Error("Datagram not signed");
|
|
267
|
+
return crypto_1.default.EdDSA.verify(this.unsigned, this._signature, publicKey);
|
|
268
|
+
}
|
|
305
269
|
toString() {
|
|
306
270
|
return (0, utils_1.decodeBase64)(this.toBytes());
|
|
307
271
|
}
|
|
308
272
|
toJSON() {
|
|
309
273
|
return {
|
|
310
|
-
id: this.id,
|
|
311
274
|
version: this.version,
|
|
312
|
-
|
|
313
|
-
receiver: this.receiver,
|
|
275
|
+
sessionTag: this.sessionTag,
|
|
314
276
|
protocol: this.protocol,
|
|
315
|
-
createdAt: this.createdAt,
|
|
316
277
|
payload: this.payload ? (0, utils_1.decodeBase64)(this.payload) : undefined,
|
|
317
278
|
signature: this._signature ? (0, utils_1.decodeBase64)(this._signature) : undefined
|
|
318
279
|
};
|
|
319
280
|
}
|
|
320
|
-
static verify(datagram, publicKey) {
|
|
321
|
-
if (!datagram._signature)
|
|
322
|
-
throw new Error("Datagram not signed");
|
|
323
|
-
return crypto_1.default.EdDSA.verify(datagram.unsigned, datagram._signature, publicKey);
|
|
324
|
-
}
|
|
325
281
|
static from(data) {
|
|
326
282
|
if (typeof data === 'string')
|
|
327
283
|
data = (0, utils_1.encodeBase64)(data);
|
|
328
284
|
if (data instanceof Uint8Array) {
|
|
329
|
-
const
|
|
285
|
+
const authTag = data.subarray(2, Datagram.headerLength);
|
|
286
|
+
const datagram = new Datagram(Protocols.decode(data.subarray(1, 2)), data.subarray(Datagram.headerLength, data.length - (data[0] & 128 ? crypto_1.default.EdDSA.signatureLength : 0)), (0, utils_1.compareBytes)(authTag, new Uint8Array(32).fill(0)) ? undefined : (0, utils_1.decodeBase64)(authTag));
|
|
330
287
|
datagram._version = data[0] & 127;
|
|
331
|
-
datagram._id = crypto_1.default.UUID.stringify(data.subarray(2, 18));
|
|
332
|
-
datagram._createdAt = (0, utils_1.bytesToNumber)(data.subarray(18, 26));
|
|
333
288
|
if (data[0] & 128)
|
|
334
289
|
datagram._signature = data.subarray(data.length - crypto_1.default.EdDSA.signatureLength);
|
|
335
290
|
return datagram;
|
|
336
291
|
}
|
|
337
292
|
else if (data instanceof Datagram) {
|
|
338
|
-
const datagram = new Datagram(data.
|
|
339
|
-
datagram._id = data.id;
|
|
293
|
+
const datagram = new Datagram(data.protocol, data.payload, data.sessionTag);
|
|
340
294
|
datagram._version = data.version;
|
|
341
|
-
datagram._createdAt = data._createdAt;
|
|
342
295
|
datagram._signature = data._signature;
|
|
343
296
|
return datagram;
|
|
344
297
|
}
|
|
@@ -348,6 +301,19 @@ class Datagram {
|
|
|
348
301
|
}
|
|
349
302
|
exports.Datagram = Datagram;
|
|
350
303
|
Datagram.version = 1;
|
|
304
|
+
Datagram.headerLength = 34;
|
|
305
|
+
class EncryptedDatagram extends Datagram {
|
|
306
|
+
constructor(protocol, sessionTag, payload) {
|
|
307
|
+
super(protocol, payload, sessionTag);
|
|
308
|
+
}
|
|
309
|
+
static from(data) {
|
|
310
|
+
const datagram = Datagram.from(data);
|
|
311
|
+
if (!datagram.sessionTag)
|
|
312
|
+
throw new Error("Datagram not encrypted");
|
|
313
|
+
return datagram;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
exports.EncryptedDatagram = EncryptedDatagram;
|
|
351
317
|
class EncryptionHeader {
|
|
352
318
|
constructor(keys, nonce) {
|
|
353
319
|
this.nonce = nonce;
|
package/package.json
CHANGED
|
@@ -1,46 +1,45 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@freesignal/protocol",
|
|
3
|
-
"version": "0.7.
|
|
4
|
-
"description": "Signal Protocol implementation in javascript",
|
|
5
|
-
"license": "GPL-3.0-or-later",
|
|
6
|
-
"author": "Christian Braghette",
|
|
7
|
-
"type": "commonjs",
|
|
8
|
-
"exports": {
|
|
9
|
-
".": {
|
|
10
|
-
"import": "./dist/index.js",
|
|
11
|
-
"require": "./dist/index.js",
|
|
12
|
-
"types": "./dist/index.d.ts"
|
|
13
|
-
},
|
|
14
|
-
"./double-ratchet": {
|
|
15
|
-
"import": "./dist/double-ratchet.js",
|
|
16
|
-
"require": "./dist/double-ratchet.js",
|
|
17
|
-
"types": "./dist/double-ratchet.d.ts"
|
|
18
|
-
},
|
|
19
|
-
"./node": {
|
|
20
|
-
"import": "./dist/node.js",
|
|
21
|
-
"require": "./dist/node.js",
|
|
22
|
-
"types": "./dist/node.d.ts"
|
|
23
|
-
},
|
|
24
|
-
"./x3dh": {
|
|
25
|
-
"import": "./dist/x3dh.js",
|
|
26
|
-
"require": "./dist/x3dh.js",
|
|
27
|
-
"types": "./dist/x3dh.d.ts"
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
|
-
"main": "./dist/index.js",
|
|
31
|
-
"scripts": {
|
|
32
|
-
"pretest": "tsc",
|
|
33
|
-
"test": "node ./dist/test.js",
|
|
34
|
-
"prepare": "tsc"
|
|
35
|
-
},
|
|
36
|
-
"dependencies": {
|
|
37
|
-
"@freesignal/crypto": "^0.3.0",
|
|
38
|
-
"@freesignal/interfaces": "^0.2.0",
|
|
39
|
-
"@freesignal/utils": "^1.4.1",
|
|
40
|
-
"easyemitter.ts": "^1.0.3"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@freesignal/protocol",
|
|
3
|
+
"version": "0.7.3",
|
|
4
|
+
"description": "Signal Protocol implementation in javascript",
|
|
5
|
+
"license": "GPL-3.0-or-later",
|
|
6
|
+
"author": "Christian Braghette",
|
|
7
|
+
"type": "commonjs",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"require": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
},
|
|
14
|
+
"./double-ratchet": {
|
|
15
|
+
"import": "./dist/double-ratchet.js",
|
|
16
|
+
"require": "./dist/double-ratchet.js",
|
|
17
|
+
"types": "./dist/double-ratchet.d.ts"
|
|
18
|
+
},
|
|
19
|
+
"./node": {
|
|
20
|
+
"import": "./dist/node.js",
|
|
21
|
+
"require": "./dist/node.js",
|
|
22
|
+
"types": "./dist/node.d.ts"
|
|
23
|
+
},
|
|
24
|
+
"./x3dh": {
|
|
25
|
+
"import": "./dist/x3dh.js",
|
|
26
|
+
"require": "./dist/x3dh.js",
|
|
27
|
+
"types": "./dist/x3dh.d.ts"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"main": "./dist/index.js",
|
|
31
|
+
"scripts": {
|
|
32
|
+
"pretest": "tsc",
|
|
33
|
+
"test": "node ./dist/test.js",
|
|
34
|
+
"prepare": "tsc"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@freesignal/crypto": "^0.3.0",
|
|
38
|
+
"@freesignal/interfaces": "^0.2.0",
|
|
39
|
+
"@freesignal/utils": "^1.4.1",
|
|
40
|
+
"easyemitter.ts": "^1.0.3"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^24.2.1"
|
|
44
|
+
}
|
|
45
|
+
}
|