@freesignal/protocol 0.7.1 → 0.7.4
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 +16 -8
- package/dist/node.js +92 -72
- package/dist/types.d.ts +13 -29
- package/dist/types.js +31 -65
- package/package.json +2 -3
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,18 +33,25 @@ 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
39
|
request?: BootstrapRequest;
|
|
40
|
+
userId?: UserId;
|
|
41
|
+
};
|
|
42
|
+
type SendEventData = {
|
|
43
|
+
session?: KeySession;
|
|
44
|
+
datagram: Datagram;
|
|
45
|
+
userId: UserId;
|
|
40
46
|
};
|
|
41
47
|
type MessageEventData = {
|
|
42
|
-
|
|
48
|
+
session: KeySession;
|
|
43
49
|
payload: Uint8Array;
|
|
44
50
|
};
|
|
45
51
|
export declare class FreeSignalNode {
|
|
46
52
|
protected readonly privateIdentityKey: PrivateIdentityKey;
|
|
47
53
|
protected readonly sessions: SessionMap;
|
|
54
|
+
protected readonly users: LocalStorage<string, string>;
|
|
48
55
|
protected readonly bundles: LocalStorage<string, KeyExchangeDataBundle>;
|
|
49
56
|
protected readonly keyExchange: KeyExchange;
|
|
50
57
|
protected readonly discovers: Set<string>;
|
|
@@ -52,6 +59,7 @@ export declare class FreeSignalNode {
|
|
|
52
59
|
protected readonly emitter: EventEmitter<"message" | "send" | "handshake" | "ping" | "bootstrap", NodeEventData>;
|
|
53
60
|
constructor(storage: Database<{
|
|
54
61
|
sessions: LocalStorage<string, ExportedKeySession>;
|
|
62
|
+
users: LocalStorage<string, string>;
|
|
55
63
|
keyExchange: LocalStorage<string, Crypto.KeyPair>;
|
|
56
64
|
bundles: LocalStorage<string, KeyExchangeDataBundle>;
|
|
57
65
|
bootstraps: LocalStorage<string, BootstrapRequest>;
|
|
@@ -68,16 +76,16 @@ export declare class FreeSignalNode {
|
|
|
68
76
|
waitHandshaked(userId: UserId | string, timeout?: number): Promise<void>;
|
|
69
77
|
get identityKey(): IdentityKey;
|
|
70
78
|
get userId(): UserId;
|
|
71
|
-
protected encrypt(receiverId: string | UserId, protocol: Protocols, data: Uint8Array): Promise<
|
|
79
|
+
protected encrypt(receiverId: string | UserId, protocol: Protocols, data: Uint8Array): Promise<SendEventData>;
|
|
72
80
|
sendHandshake(data: KeyExchangeData): Promise<void>;
|
|
73
|
-
sendHandshake(
|
|
81
|
+
sendHandshake(session: KeySession): Promise<void>;
|
|
74
82
|
sendData<T>(receiverId: string | UserId, data: T): Promise<void>;
|
|
75
|
-
sendRelay(receiverId: string | UserId, data: Datagram): Promise<void>;
|
|
83
|
+
sendRelay(relayId: string | UserId, receiverId: string | UserId, data: Datagram): Promise<void>;
|
|
76
84
|
sendPing(receiverId: string | UserId): Promise<void>;
|
|
77
85
|
sendDiscover(receiverId: string | UserId, discoverId: string | UserId): Promise<void>;
|
|
78
86
|
sendBootstrap(receiverId: string | UserId): Promise<void>;
|
|
79
|
-
protected decrypt(datagram: Datagram): Promise<
|
|
80
|
-
protected open(datagram: Datagram | Uint8Array): Promise<void>;
|
|
87
|
+
protected decrypt(datagram: EncryptedDatagram | Datagram | Uint8Array): Promise<MessageEventData>;
|
|
88
|
+
protected open(datagram: Datagram | EncryptedDatagram | Uint8Array): Promise<void>;
|
|
81
89
|
}
|
|
82
90
|
declare class SessionMap implements LocalStorage<string, KeySession> {
|
|
83
91
|
readonly storage: LocalStorage<string, ExportedKeySession>;
|
package/dist/node.js
CHANGED
|
@@ -82,9 +82,9 @@ 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({
|
|
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
86
|
this.sendHandler = (data) => this.onSend(data.data.datagram.toBytes());
|
|
87
|
-
this.handshakeHandler = (data) => this.onHandshake(types_1.UserId.from(data.data.
|
|
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
88
|
this.bootstrapHandler = (data) => { var _a; return this.onRequest((_a = data.data) === null || _a === void 0 ? void 0 : _a.request); };
|
|
89
89
|
this.onMessage = () => { };
|
|
90
90
|
this.onSend = () => { };
|
|
@@ -92,6 +92,7 @@ class FreeSignalNode {
|
|
|
92
92
|
this.onRequest = () => { };
|
|
93
93
|
this.privateIdentityKey = privateIdentityKey !== null && privateIdentityKey !== void 0 ? privateIdentityKey : (0, _1.createIdentity)();
|
|
94
94
|
this.sessions = new SessionMap(storage.sessions);
|
|
95
|
+
this.users = storage.users;
|
|
95
96
|
this.keyExchange = new x3dh_1.KeyExchange(storage.keyExchange, this.privateIdentityKey);
|
|
96
97
|
this.bundles = storage.bundles;
|
|
97
98
|
this.bootstraps = storage.bootstraps;
|
|
@@ -105,10 +106,10 @@ class FreeSignalNode {
|
|
|
105
106
|
}
|
|
106
107
|
waitHandshaked(userId, timeout) {
|
|
107
108
|
return __awaiter(this, void 0, void 0, function* () {
|
|
108
|
-
var _a;
|
|
109
|
+
var _a, _b;
|
|
109
110
|
if (timeout)
|
|
110
111
|
setTimeout(() => { throw new Error(); }, timeout);
|
|
111
|
-
while (((_a = (yield this.emitter.wait('handshake', timeout))) === null || _a === void 0 ? void 0 : _a.
|
|
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())
|
|
112
113
|
;
|
|
113
114
|
});
|
|
114
115
|
}
|
|
@@ -120,55 +121,58 @@ class FreeSignalNode {
|
|
|
120
121
|
}
|
|
121
122
|
encrypt(receiverId, protocol, data) {
|
|
122
123
|
return __awaiter(this, void 0, void 0, function* () {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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);
|
|
126
128
|
if (!session)
|
|
127
|
-
throw new Error("Session not found for
|
|
129
|
+
throw new Error("Session not found for sessionTag: " + sessionTag);
|
|
128
130
|
const encrypted = (0, types_1.encryptData)(session, data);
|
|
129
|
-
this.sessions.set(receiverId, session);
|
|
130
|
-
return
|
|
131
|
+
this.sessions.set(receiverId.toString(), session);
|
|
132
|
+
return { session, userId: types_1.UserId.from(receiverId), datagram: new types_1.EncryptedDatagram(protocol, session.sessionTag, encrypted).sign(this.privateIdentityKey.signatureKey) };
|
|
131
133
|
});
|
|
132
134
|
}
|
|
133
135
|
sendHandshake(data) {
|
|
134
136
|
return __awaiter(this, void 0, void 0, function* () {
|
|
135
|
-
|
|
136
|
-
if (typeof data === 'string' || data instanceof types_1.UserId) {
|
|
137
|
+
if (data instanceof double_ratchet_1.KeySession) {
|
|
137
138
|
//console.debug("Sending Handshake Ack");
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const datagram = yield this.encrypt(userId, types_1.Protocols.HANDSHAKE, crypto_1.default.ECDH.scalarMult(this.privateIdentityKey.exchangeKey, identityKey.exchangeKey));
|
|
143
|
-
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)));
|
|
144
143
|
return;
|
|
145
144
|
}
|
|
146
145
|
//console.debug("Sending Handshake Syn");
|
|
147
146
|
const { session, message } = yield this.keyExchange.digestData(data, (0, utils_1.encodeData)(yield this.keyExchange.generateBundle()));
|
|
148
|
-
yield this.sessions.set(session.
|
|
149
|
-
|
|
150
|
-
|
|
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, userId: session.userId });
|
|
151
151
|
});
|
|
152
152
|
}
|
|
153
153
|
sendData(receiverId, data) {
|
|
154
154
|
return __awaiter(this, void 0, void 0, function* () {
|
|
155
155
|
//console.debug("Sending Data");
|
|
156
|
-
|
|
157
|
-
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)));
|
|
158
157
|
});
|
|
159
158
|
}
|
|
160
|
-
sendRelay(receiverId, data) {
|
|
159
|
+
sendRelay(relayId, receiverId, data) {
|
|
161
160
|
return __awaiter(this, void 0, void 0, function* () {
|
|
162
161
|
//console.debug("Sending Relay");
|
|
163
|
-
|
|
164
|
-
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())));
|
|
165
163
|
});
|
|
166
164
|
}
|
|
167
165
|
sendPing(receiverId) {
|
|
168
166
|
return __awaiter(this, void 0, void 0, function* () {
|
|
169
167
|
//console.debug("Sending Ping");
|
|
170
|
-
const
|
|
171
|
-
|
|
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, userId: session.userId });
|
|
172
176
|
});
|
|
173
177
|
}
|
|
174
178
|
sendDiscover(receiverId, discoverId) {
|
|
@@ -183,8 +187,7 @@ class FreeSignalNode {
|
|
|
183
187
|
discoverId
|
|
184
188
|
};
|
|
185
189
|
this.discovers.add(receiverId);
|
|
186
|
-
|
|
187
|
-
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)));
|
|
188
191
|
});
|
|
189
192
|
}
|
|
190
193
|
sendBootstrap(receiverId) {
|
|
@@ -192,69 +195,80 @@ class FreeSignalNode {
|
|
|
192
195
|
//console.debug("Sending Bootstrap");
|
|
193
196
|
if (yield this.sessions.has(receiverId.toString()))
|
|
194
197
|
throw new Error("Session exists");
|
|
195
|
-
const datagram = new types_1.Datagram(
|
|
196
|
-
this.emitter.emit('send', {
|
|
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, userId: types_1.UserId.from(receiverId) });
|
|
197
200
|
});
|
|
198
201
|
}
|
|
199
202
|
decrypt(datagram) {
|
|
200
203
|
return __awaiter(this, void 0, void 0, function* () {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
if (!types_1.Datagram.verify(datagram, identityKey.signatureKey))
|
|
206
|
-
throw new Error("Signature not verified");
|
|
207
|
-
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);
|
|
208
208
|
if (!session)
|
|
209
|
-
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");
|
|
210
212
|
if (!datagram.payload)
|
|
211
213
|
throw new Error("Missing payload");
|
|
212
214
|
const decrypted = (0, types_1.decryptData)(session, datagram.payload);
|
|
213
|
-
this.sessions.set(datagram.
|
|
214
|
-
return decrypted;
|
|
215
|
+
this.sessions.set(datagram.sessionTag, session);
|
|
216
|
+
return { session, payload: decrypted };
|
|
215
217
|
});
|
|
216
218
|
}
|
|
217
219
|
open(datagram) {
|
|
218
220
|
return __awaiter(this, void 0, void 0, function* () {
|
|
219
|
-
var _a;
|
|
220
221
|
if (datagram instanceof Uint8Array)
|
|
221
222
|
datagram = types_1.Datagram.from(datagram);
|
|
222
223
|
switch (datagram.protocol) {
|
|
223
|
-
case types_1.Protocols.HANDSHAKE:
|
|
224
|
-
|
|
224
|
+
case types_1.Protocols.HANDSHAKE: {
|
|
225
|
+
const encrypted = types_1.EncryptedDatagram.from(datagram);
|
|
226
|
+
if (!encrypted.payload)
|
|
225
227
|
throw new Error("Missing payload");
|
|
226
|
-
if (yield this.sessions.has(
|
|
228
|
+
if (yield this.sessions.has(encrypted.sessionTag)) {
|
|
227
229
|
//console.debug("Opening Handshake Ack");
|
|
228
|
-
const
|
|
229
|
-
const
|
|
230
|
-
if (!
|
|
231
|
-
throw new Error("
|
|
232
|
-
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)))
|
|
233
235
|
throw new Error("Error validating handshake data");
|
|
234
|
-
this.emitter.emit('handshake', {
|
|
236
|
+
this.emitter.emit('handshake', { session });
|
|
235
237
|
return;
|
|
236
238
|
}
|
|
237
239
|
//console.debug("Opening Handshake Syn");
|
|
238
|
-
const data = (0, utils_1.decodeData)(
|
|
239
|
-
if (!
|
|
240
|
+
const data = (0, utils_1.decodeData)(encrypted.payload);
|
|
241
|
+
if (!datagram.verify(types_1.IdentityKey.from(data.identityKey).signatureKey))
|
|
240
242
|
throw new Error("Signature not verified");
|
|
241
243
|
const { session, associatedData } = yield this.keyExchange.digestMessage(data);
|
|
242
|
-
yield this.sessions.set(session.
|
|
244
|
+
yield this.sessions.set(session.sessionTag, session);
|
|
245
|
+
yield this.users.set(session.userId.toString(), session.sessionTag);
|
|
243
246
|
yield this.bundles.set(session.userId.toString(), (0, utils_1.decodeData)(associatedData));
|
|
244
|
-
yield this.sendHandshake(session
|
|
245
|
-
this.emitter.emit('handshake', {
|
|
247
|
+
yield this.sendHandshake(session);
|
|
248
|
+
this.emitter.emit('handshake', { session });
|
|
246
249
|
return;
|
|
250
|
+
}
|
|
247
251
|
case types_1.Protocols.MESSAGE:
|
|
248
252
|
//console.debug("Opening Message");
|
|
249
|
-
this.emitter.emit('message',
|
|
253
|
+
this.emitter.emit('message', yield this.decrypt(datagram));
|
|
250
254
|
return;
|
|
251
|
-
case types_1.Protocols.RELAY:
|
|
255
|
+
case types_1.Protocols.RELAY: {
|
|
252
256
|
//console.debug("Opening Relay");
|
|
253
|
-
|
|
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), userId: session.userId });
|
|
254
266
|
return;
|
|
255
|
-
|
|
267
|
+
}
|
|
268
|
+
case types_1.Protocols.DISCOVER: {
|
|
256
269
|
//console.debug("Opening Discover");
|
|
257
|
-
const
|
|
270
|
+
const { session, payload } = yield this.decrypt(datagram);
|
|
271
|
+
const message = (0, utils_1.decodeData)(payload);
|
|
258
272
|
if (message.type === types_1.DiscoverType.REQUEST && message.discoverId && !(yield this.sessions.has(message.discoverId))) {
|
|
259
273
|
let data;
|
|
260
274
|
if (message.discoverId === this.userId.toString()) {
|
|
@@ -279,7 +293,7 @@ class FreeSignalNode {
|
|
|
279
293
|
};
|
|
280
294
|
}
|
|
281
295
|
const response = { type: types_1.DiscoverType.RESPONSE, discoverId: message.discoverId, data };
|
|
282
|
-
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)));
|
|
283
297
|
}
|
|
284
298
|
else if (message.type === types_1.DiscoverType.RESPONSE && this.discovers.has(message.discoverId)) {
|
|
285
299
|
this.discovers.delete(message.discoverId);
|
|
@@ -287,24 +301,30 @@ class FreeSignalNode {
|
|
|
287
301
|
yield this.sendHandshake(message.data);
|
|
288
302
|
}
|
|
289
303
|
return;
|
|
290
|
-
|
|
304
|
+
}
|
|
305
|
+
case types_1.Protocols.BOOTSTRAP: {
|
|
291
306
|
//console.debug("Opening Bootstrap");
|
|
292
307
|
if (!datagram.payload)
|
|
293
308
|
throw new Error("Invalid Bootstrap");
|
|
294
309
|
const keyExchangeData = (0, utils_1.decodeData)(datagram.payload);
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
310
|
+
const userId = types_1.UserId.fromKey(keyExchangeData.identityKey);
|
|
311
|
+
const request = new BootstrapRequest(userId, keyExchangeData);
|
|
312
|
+
request.onChange = (event) => {
|
|
313
|
+
var _a;
|
|
299
314
|
if (!request.data)
|
|
300
315
|
throw new Error("Error sending handshake");
|
|
301
|
-
this.sendHandshake(
|
|
316
|
+
this.sendHandshake((_a = event.data) === null || _a === void 0 ? void 0 : _a.data);
|
|
302
317
|
};
|
|
303
|
-
yield this.bootstraps.set(
|
|
304
|
-
this.emitter.emit('bootstrap', {
|
|
318
|
+
yield this.bootstraps.set(userId.toString(), request);
|
|
319
|
+
this.emitter.emit('bootstrap', { request });
|
|
305
320
|
return;
|
|
321
|
+
}
|
|
306
322
|
case types_1.Protocols.PING:
|
|
307
|
-
|
|
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 });
|
|
308
328
|
return;
|
|
309
329
|
default:
|
|
310
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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@freesignal/protocol",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
4
4
|
"description": "Signal Protocol implementation in javascript",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"author": "Christian Braghette",
|
|
@@ -37,8 +37,7 @@
|
|
|
37
37
|
"@freesignal/crypto": "^0.3.0",
|
|
38
38
|
"@freesignal/interfaces": "^0.2.0",
|
|
39
39
|
"@freesignal/utils": "^1.4.1",
|
|
40
|
-
"easyemitter.ts": "^1.0.3"
|
|
41
|
-
"semaphore.ts": "^0.2.0"
|
|
40
|
+
"easyemitter.ts": "^1.0.3"
|
|
42
41
|
},
|
|
43
42
|
"devDependencies": {
|
|
44
43
|
"@types/node": "^24.2.1"
|