@freesignal/protocol 0.2.11 → 0.3.2
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/{double-ratchet.d.ts → dist/double-ratchet.d.ts} +26 -38
- package/dist/double-ratchet.js +345 -0
- package/{index.d.ts → dist/index.d.ts} +11 -13
- package/{index.js → dist/index.js} +12 -10
- package/dist/node.d.ts +53 -0
- package/dist/node.js +242 -0
- package/dist/test.d.ts +1 -0
- package/dist/test.js +41 -0
- package/{types.d.ts → dist/types.d.ts} +33 -9
- package/{types.js → dist/types.js} +50 -33
- package/{x3dh.d.ts → dist/x3dh.d.ts} +2 -1
- package/{x3dh.js → dist/x3dh.js} +9 -8
- package/package.json +30 -6
- package/double-ratchet.js +0 -294
- package/node.d.ts +0 -27
- package/node.js +0 -105
- package/test.js +0 -25
package/dist/node.js
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
12
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
13
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
14
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
15
|
+
};
|
|
16
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
17
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
18
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
19
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
20
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
21
|
+
};
|
|
22
|
+
var _BootstrapRequest_status;
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.FreeSignalNode = void 0;
|
|
25
|
+
const types_1 = require("./types");
|
|
26
|
+
const x3dh_1 = require("./x3dh");
|
|
27
|
+
const double_ratchet_1 = require("./double-ratchet");
|
|
28
|
+
const _1 = require(".");
|
|
29
|
+
const utils_1 = require("@freesignal/utils");
|
|
30
|
+
class BootstrapRequest {
|
|
31
|
+
constructor(senderId, data, acceptFn) {
|
|
32
|
+
this.senderId = senderId;
|
|
33
|
+
this.data = data;
|
|
34
|
+
this.acceptFn = acceptFn;
|
|
35
|
+
_BootstrapRequest_status.set(this, 'pending');
|
|
36
|
+
}
|
|
37
|
+
get status() {
|
|
38
|
+
return __classPrivateFieldGet(this, _BootstrapRequest_status, "f");
|
|
39
|
+
}
|
|
40
|
+
accept() {
|
|
41
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
42
|
+
if (this.status === 'pending')
|
|
43
|
+
__classPrivateFieldSet(this, _BootstrapRequest_status, 'accepted', "f");
|
|
44
|
+
if (__classPrivateFieldGet(this, _BootstrapRequest_status, "f") === 'accepted')
|
|
45
|
+
return yield this.acceptFn(this.data);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
deny() {
|
|
49
|
+
if (this.status === 'pending')
|
|
50
|
+
__classPrivateFieldSet(this, _BootstrapRequest_status, 'denied', "f");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
_BootstrapRequest_status = new WeakMap();
|
|
55
|
+
class FreeSignalNode {
|
|
56
|
+
constructor(storage, privateIdentityKey) {
|
|
57
|
+
this.discovers = new Set();
|
|
58
|
+
this.bootstraps = new Set();
|
|
59
|
+
this.privateIdentityKey = privateIdentityKey !== null && privateIdentityKey !== void 0 ? privateIdentityKey : (0, _1.createIdentity)();
|
|
60
|
+
this.sessions = new SessionMap(storage.sessions);
|
|
61
|
+
this.keyExchange = new x3dh_1.KeyExchange({ keys: storage.keyExchange, sessions: storage.sessions }, this.privateIdentityKey);
|
|
62
|
+
this.users = storage.users;
|
|
63
|
+
this.bundles = storage.bundles;
|
|
64
|
+
}
|
|
65
|
+
get identityKey() {
|
|
66
|
+
return this.privateIdentityKey.identityKey;
|
|
67
|
+
}
|
|
68
|
+
get userId() {
|
|
69
|
+
return types_1.UserId.fromKey(this.identityKey);
|
|
70
|
+
}
|
|
71
|
+
get requests() {
|
|
72
|
+
return Array.from(this.bootstraps.values());
|
|
73
|
+
}
|
|
74
|
+
encrypt(receiverId, protocol, data) {
|
|
75
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
76
|
+
if (receiverId instanceof types_1.UserId)
|
|
77
|
+
receiverId = receiverId.toString();
|
|
78
|
+
const session = yield this.sessions.get(receiverId);
|
|
79
|
+
if (!session)
|
|
80
|
+
throw new Error("Session not found for user: " + receiverId);
|
|
81
|
+
return new types_1.Datagram(this.userId.toString(), receiverId, protocol, yield session.encrypt(data)).sign(this.privateIdentityKey.signatureKey);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
packHandshake(data) {
|
|
85
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
86
|
+
const { session, message, identityKey } = yield this.keyExchange.digestData(data, (0, utils_1.encodeData)(yield this.keyExchange.generateBundle()));
|
|
87
|
+
const remoteId = types_1.UserId.fromKey(identityKey);
|
|
88
|
+
yield this.users.set(remoteId.toString(), identityKey);
|
|
89
|
+
yield this.sessions.set(remoteId.toString(), session);
|
|
90
|
+
return new types_1.Datagram(this.userId.toString(), types_1.UserId.fromKey(data.identityKey).toString(), types_1.Protocols.HANDSHAKE, (0, utils_1.encodeData)(message)).sign(this.privateIdentityKey.signatureKey);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
packData(receiverId, data) {
|
|
94
|
+
return this.encrypt(receiverId, types_1.Protocols.MESSAGE, (0, utils_1.encodeData)(data));
|
|
95
|
+
}
|
|
96
|
+
packRelay(receiverId, data) {
|
|
97
|
+
return this.encrypt(receiverId, types_1.Protocols.RELAY, (0, utils_1.encodeData)(data));
|
|
98
|
+
}
|
|
99
|
+
packDiscover(receiverId, discoverId) {
|
|
100
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
101
|
+
if (receiverId instanceof types_1.UserId)
|
|
102
|
+
receiverId = receiverId.toString();
|
|
103
|
+
if (discoverId instanceof types_1.UserId)
|
|
104
|
+
discoverId = discoverId.toString();
|
|
105
|
+
const message = {
|
|
106
|
+
type: types_1.DiscoverType.REQUEST,
|
|
107
|
+
discoverId
|
|
108
|
+
};
|
|
109
|
+
this.discovers.add(receiverId);
|
|
110
|
+
return this.encrypt(receiverId, types_1.Protocols.DISCOVER, (0, utils_1.encodeData)(message));
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
packBootstrap(receiverId) {
|
|
114
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
115
|
+
return new types_1.Datagram(this.userId.toString(), receiverId.toString(), types_1.Protocols.BOOTSTRAP, (0, utils_1.encodeData)(yield this.keyExchange.generateData()));
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
decrypt(datagram) {
|
|
119
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
120
|
+
const signatureKey = yield this.users.get(datagram.sender);
|
|
121
|
+
if (!signatureKey)
|
|
122
|
+
throw new Error("User IdentityKey not found");
|
|
123
|
+
if (!types_1.Datagram.verify(datagram, signatureKey.signatureKey))
|
|
124
|
+
throw new Error("Signature not verified");
|
|
125
|
+
const session = yield this.sessions.get(datagram.sender);
|
|
126
|
+
if (!session)
|
|
127
|
+
throw new Error("Session not found for user: " + datagram.sender);
|
|
128
|
+
if (!datagram.payload)
|
|
129
|
+
throw new Error("Missing payload");
|
|
130
|
+
const decrypted = yield session.decrypt(datagram.payload);
|
|
131
|
+
if (!decrypted)
|
|
132
|
+
throw new Error("Decryption failed");
|
|
133
|
+
return decrypted;
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
open(datagram) {
|
|
137
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
138
|
+
if (datagram instanceof Uint8Array)
|
|
139
|
+
datagram = types_1.Datagram.from(datagram);
|
|
140
|
+
switch (datagram.protocol) {
|
|
141
|
+
case types_1.Protocols.HANDSHAKE:
|
|
142
|
+
if (!datagram.payload)
|
|
143
|
+
throw new Error("Missing payload");
|
|
144
|
+
const data = (0, utils_1.decodeData)(datagram.payload);
|
|
145
|
+
if (!types_1.Datagram.verify(datagram, types_1.IdentityKey.from(data.identityKey).signatureKey))
|
|
146
|
+
throw new Error("Signature not verified");
|
|
147
|
+
const { session, identityKey, associatedData } = yield this.keyExchange.digestMessage(data);
|
|
148
|
+
const userId = types_1.UserId.fromKey(identityKey);
|
|
149
|
+
yield this.users.set(userId.toString(), identityKey);
|
|
150
|
+
yield this.sessions.set(userId.toString(), session);
|
|
151
|
+
yield this.bundles.set(userId.toString(), (0, utils_1.decodeData)(associatedData));
|
|
152
|
+
return;
|
|
153
|
+
case types_1.Protocols.MESSAGE:
|
|
154
|
+
return (0, utils_1.decodeData)(yield this.decrypt(datagram));
|
|
155
|
+
case types_1.Protocols.RELAY:
|
|
156
|
+
return (0, utils_1.decodeData)(yield this.decrypt(datagram));
|
|
157
|
+
case types_1.Protocols.DISCOVER:
|
|
158
|
+
const message = (0, utils_1.decodeData)(yield this.decrypt(datagram));
|
|
159
|
+
if (message.type === types_1.DiscoverType.REQUEST && message.discoverId && !(yield this.users.has(message.discoverId))) {
|
|
160
|
+
let data;
|
|
161
|
+
if (message.discoverId === this.userId.toString()) {
|
|
162
|
+
data = yield this.keyExchange.generateData();
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
const bundle = yield this.bundles.get(message.discoverId);
|
|
166
|
+
if (!bundle)
|
|
167
|
+
return;
|
|
168
|
+
const { version, identityKey, signedPreKey, signature } = bundle;
|
|
169
|
+
const onetimePreKey = bundle.onetimePreKeys.shift();
|
|
170
|
+
if (!onetimePreKey) {
|
|
171
|
+
yield this.bundles.delete(message.discoverId);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
data = {
|
|
175
|
+
version,
|
|
176
|
+
identityKey,
|
|
177
|
+
signedPreKey,
|
|
178
|
+
signature,
|
|
179
|
+
onetimePreKey
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
const response = { type: types_1.DiscoverType.RESPONSE, discoverId: message.discoverId, data };
|
|
183
|
+
return yield this.encrypt(datagram.sender, types_1.Protocols.DISCOVER, (0, utils_1.encodeData)(response));
|
|
184
|
+
}
|
|
185
|
+
else if (message.type === types_1.DiscoverType.RESPONSE && this.discovers.has(message.discoverId)) {
|
|
186
|
+
this.discovers.delete(message.discoverId);
|
|
187
|
+
return message.data;
|
|
188
|
+
}
|
|
189
|
+
return;
|
|
190
|
+
case types_1.Protocols.BOOTSTRAP:
|
|
191
|
+
if (datagram.payload) {
|
|
192
|
+
const data = (0, utils_1.decodeData)(datagram.payload);
|
|
193
|
+
if (!(0, utils_1.compareBytes)(types_1.UserId.fromKey(data.identityKey).toBytes(), (0, utils_1.encodeBase64)(datagram.sender)))
|
|
194
|
+
return;
|
|
195
|
+
this.bootstraps.add(new BootstrapRequest(datagram.sender, data, (data) => this.packHandshake(data)));
|
|
196
|
+
}
|
|
197
|
+
;
|
|
198
|
+
return;
|
|
199
|
+
default:
|
|
200
|
+
throw new Error("Invalid protocol");
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
exports.FreeSignalNode = FreeSignalNode;
|
|
206
|
+
class SessionMap {
|
|
207
|
+
constructor(storage, maxSize = 50) {
|
|
208
|
+
this.storage = storage;
|
|
209
|
+
this.maxSize = maxSize;
|
|
210
|
+
this.cache = new Map();
|
|
211
|
+
}
|
|
212
|
+
set(key, value) {
|
|
213
|
+
this.cache.set(key, value);
|
|
214
|
+
return this.storage.set(key, value.toJSON());
|
|
215
|
+
}
|
|
216
|
+
get(key) {
|
|
217
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
218
|
+
const session = this.cache.get(key);
|
|
219
|
+
if (!session) {
|
|
220
|
+
const sessionData = yield this.storage.get(key);
|
|
221
|
+
if (!sessionData)
|
|
222
|
+
return undefined;
|
|
223
|
+
return double_ratchet_1.KeySession.from(sessionData, this.storage);
|
|
224
|
+
}
|
|
225
|
+
return session;
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
has(key) {
|
|
229
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
230
|
+
return this.cache.has(key) || (yield this.storage.has(key));
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
delete(key) {
|
|
234
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
235
|
+
return this.cache.delete(key) || (yield this.storage.delete(key));
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
clear() {
|
|
239
|
+
this.cache.clear();
|
|
240
|
+
return this.storage.clear();
|
|
241
|
+
}
|
|
242
|
+
}
|
package/dist/test.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/test.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
const utils_1 = require("@freesignal/utils");
|
|
16
|
+
const _1 = require(".");
|
|
17
|
+
const crypto_1 = __importDefault(require("@freesignal/crypto"));
|
|
18
|
+
console.log("FreeSignal protocol test");
|
|
19
|
+
const bob = (0, _1.createNode)({ keyExchange: new _1.AsyncMap(), sessions: new _1.AsyncMap(), users: new _1.AsyncMap(), bundles: new _1.AsyncMap() });
|
|
20
|
+
const alice = (0, _1.createNode)({ keyExchange: new _1.AsyncMap(), sessions: new _1.AsyncMap(), users: new _1.AsyncMap(), bundles: new _1.AsyncMap() });
|
|
21
|
+
setImmediate(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
22
|
+
const bobBootstrap = yield bob.packBootstrap(alice.userId);
|
|
23
|
+
yield alice.open(bobBootstrap);
|
|
24
|
+
const bootstraps = yield Promise.all(alice.requests.map(request => request.accept()));
|
|
25
|
+
const aliceHandshake = bootstraps.filter(value => (value === null || value === void 0 ? void 0 : value.receiver) === bob.userId.toString())[0];
|
|
26
|
+
if (!aliceHandshake)
|
|
27
|
+
throw new Error("Bootstrap Failed");
|
|
28
|
+
yield bob.open(aliceHandshake);
|
|
29
|
+
const first = (yield bob.packData(alice.userId, "Hi Alice!")).toBytes();
|
|
30
|
+
console.log("Bob: ", yield alice.open(first));
|
|
31
|
+
const second = yield alice.packData(bob.userId, "Hi Bob!");
|
|
32
|
+
console.log("Alice: ", yield bob.open(second));
|
|
33
|
+
const third = yield Promise.all(["How are you?", "How are this days?", "For me it's a good time"].map(msg => bob.packData(alice.userId, msg)));
|
|
34
|
+
third.forEach((data) => __awaiter(void 0, void 0, void 0, function* () {
|
|
35
|
+
console.log("Bob: ", yield alice.open(data));
|
|
36
|
+
}));
|
|
37
|
+
const fourth = yield alice.packData(bob.userId, "Not so bad my man");
|
|
38
|
+
console.log("Alice: ", yield bob.open(fourth));
|
|
39
|
+
const testone = yield Promise.all(Array(400).fill(0).map(() => alice.packData(bob.userId, (0, utils_1.decodeBase64)(crypto_1.default.randomBytes(64)))));
|
|
40
|
+
console.log((yield bob.open(testone[350])));
|
|
41
|
+
}));
|
|
@@ -16,13 +16,13 @@
|
|
|
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 { LocalStorage, Encodable } from "@freesignal/interfaces";
|
|
20
|
-
export declare class UserId {
|
|
19
|
+
import { LocalStorage, Encodable, KeyExchangeData } from "@freesignal/interfaces";
|
|
20
|
+
export declare class UserId implements Encodable {
|
|
21
21
|
private readonly array;
|
|
22
22
|
private constructor();
|
|
23
23
|
toString(): string;
|
|
24
24
|
toJSON(): string;
|
|
25
|
-
|
|
25
|
+
toBytes(): Uint8Array;
|
|
26
26
|
static fromKey(identityKey: string | Uint8Array | IdentityKey): UserId;
|
|
27
27
|
static from(userId: string | Uint8Array | UserId): UserId;
|
|
28
28
|
}
|
|
@@ -51,12 +51,22 @@ export declare namespace PrivateIdentityKey {
|
|
|
51
51
|
function from(identityKey: PrivateIdentityKey | Uint8Array | string): PrivateIdentityKey;
|
|
52
52
|
function from(signatureKey: Uint8Array | string, exchangeKey: Uint8Array | string): PrivateIdentityKey;
|
|
53
53
|
}
|
|
54
|
+
export declare enum DiscoverType {
|
|
55
|
+
REQUEST = 0,
|
|
56
|
+
RESPONSE = 1
|
|
57
|
+
}
|
|
58
|
+
export interface DiscoverMessage {
|
|
59
|
+
type: DiscoverType;
|
|
60
|
+
discoverId: string;
|
|
61
|
+
data?: KeyExchangeData;
|
|
62
|
+
}
|
|
54
63
|
export declare enum Protocols {
|
|
55
64
|
NULL = "",
|
|
56
65
|
MESSAGE = "/freesignal/message",
|
|
57
66
|
RELAY = "/freesignal/relay",
|
|
58
67
|
HANDSHAKE = "/freesignal/handshake",
|
|
59
|
-
DISCOVER = "/freesignal/discover"
|
|
68
|
+
DISCOVER = "/freesignal/discover",
|
|
69
|
+
BOOTSTRAP = "/freesignal/bootstrap"
|
|
60
70
|
}
|
|
61
71
|
export declare namespace Protocols {
|
|
62
72
|
function isProtocol(protocol: any): boolean;
|
|
@@ -65,6 +75,19 @@ export declare namespace Protocols {
|
|
|
65
75
|
function encode(protocol: Protocols, length?: number): Uint8Array;
|
|
66
76
|
function decode(array: Uint8Array): Protocols;
|
|
67
77
|
}
|
|
78
|
+
interface DatagramJSON {
|
|
79
|
+
id: string;
|
|
80
|
+
version: number;
|
|
81
|
+
sender: string;
|
|
82
|
+
receiver: string;
|
|
83
|
+
protocol: Protocols;
|
|
84
|
+
createdAt: number;
|
|
85
|
+
payload: string | undefined;
|
|
86
|
+
signature: string | undefined;
|
|
87
|
+
}
|
|
88
|
+
export interface SignedDatagram extends Datagram {
|
|
89
|
+
signature: string;
|
|
90
|
+
}
|
|
68
91
|
export declare class Datagram implements Encodable {
|
|
69
92
|
static version: number;
|
|
70
93
|
private _id;
|
|
@@ -75,20 +98,20 @@ export declare class Datagram implements Encodable {
|
|
|
75
98
|
private _createdAt;
|
|
76
99
|
private _payload?;
|
|
77
100
|
private _signature?;
|
|
78
|
-
private secretKey?;
|
|
79
101
|
private static headerOffset;
|
|
80
102
|
constructor(sender: Uint8Array | string, receiver: Uint8Array | string, protocol: Protocols, payload?: Uint8Array | Encodable);
|
|
81
103
|
get id(): string;
|
|
82
104
|
get version(): number;
|
|
83
105
|
get createdAt(): number;
|
|
84
|
-
get signed(): boolean;
|
|
85
|
-
get signature(): string | undefined;
|
|
86
106
|
set payload(data: Uint8Array);
|
|
87
107
|
get payload(): Uint8Array | undefined;
|
|
108
|
+
get signature(): string | undefined;
|
|
109
|
+
private get unsigned();
|
|
88
110
|
toBytes(): Uint8Array;
|
|
89
|
-
sign(secretKey: Uint8Array):
|
|
111
|
+
sign(secretKey: Uint8Array): SignedDatagram;
|
|
90
112
|
toString(): string;
|
|
91
|
-
toJSON():
|
|
113
|
+
toJSON(): DatagramJSON;
|
|
114
|
+
static verify(datagram: Datagram, publicKey: Uint8Array): boolean;
|
|
92
115
|
static from(data: Uint8Array | Datagram | string): Datagram;
|
|
93
116
|
}
|
|
94
117
|
/**
|
|
@@ -163,3 +186,4 @@ export declare class AsyncMap<K, V> implements LocalStorage<K, V> {
|
|
|
163
186
|
clear(): Promise<void>;
|
|
164
187
|
entries(): ArrayIterator<[K, V]>;
|
|
165
188
|
}
|
|
189
|
+
export {};
|
|
@@ -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.Datagram = exports.Protocols = exports.PrivateIdentityKey = exports.IdentityKey = exports.UserId = void 0;
|
|
33
|
+
exports.AsyncMap = exports.EncryptedData = exports.Datagram = exports.Protocols = exports.DiscoverType = exports.PrivateIdentityKey = exports.IdentityKey = exports.UserId = void 0;
|
|
34
34
|
const utils_1 = require("@freesignal/utils");
|
|
35
35
|
const crypto_1 = __importDefault(require("@freesignal/crypto"));
|
|
36
36
|
const double_ratchet_1 = require("./double-ratchet");
|
|
@@ -45,7 +45,7 @@ class UserId {
|
|
|
45
45
|
toJSON() {
|
|
46
46
|
return this.toString();
|
|
47
47
|
}
|
|
48
|
-
|
|
48
|
+
toBytes() {
|
|
49
49
|
return this.array;
|
|
50
50
|
}
|
|
51
51
|
static fromKey(identityKey) {
|
|
@@ -53,7 +53,7 @@ class UserId {
|
|
|
53
53
|
identityKey = (0, utils_1.encodeBase64)(identityKey);
|
|
54
54
|
else if (IdentityKey.isIdentityKeys(identityKey))
|
|
55
55
|
identityKey = identityKey.toBytes();
|
|
56
|
-
return new UserId(crypto_1.default.
|
|
56
|
+
return new UserId(crypto_1.default.hkdf(identityKey, new Uint8Array(32).fill(0), "/freesignal/userid"));
|
|
57
57
|
}
|
|
58
58
|
static from(userId) {
|
|
59
59
|
if (typeof userId === 'string')
|
|
@@ -88,7 +88,7 @@ var IdentityKey;
|
|
|
88
88
|
return UserId.fromKey(this.toBytes()).toString();
|
|
89
89
|
}
|
|
90
90
|
toBytes() {
|
|
91
|
-
return (0, utils_1.
|
|
91
|
+
return (0, utils_1.concatBytes)((0, utils_1.numberToBytes)(this.info, 1), this.signatureKey, this.exchangeKey);
|
|
92
92
|
}
|
|
93
93
|
toString() {
|
|
94
94
|
return (0, utils_1.decodeBase64)(this.toBytes());
|
|
@@ -110,7 +110,7 @@ var IdentityKey;
|
|
|
110
110
|
else
|
|
111
111
|
return key;
|
|
112
112
|
});
|
|
113
|
-
return new IdentityKeyConstructor(keys.length === 2 ? (0, utils_1.
|
|
113
|
+
return new IdentityKeyConstructor(keys.length === 2 ? (0, utils_1.concatBytes)((0, utils_1.numberToBytes)(info + IdentityKey.version, 1), ...keys) : keys[0]);
|
|
114
114
|
}
|
|
115
115
|
IdentityKey.from = from;
|
|
116
116
|
})(IdentityKey || (exports.IdentityKey = IdentityKey = {}));
|
|
@@ -121,8 +121,8 @@ var PrivateIdentityKey;
|
|
|
121
121
|
PrivateIdentityKey.version = 1;
|
|
122
122
|
class PrivateIdentityKeyConstructor {
|
|
123
123
|
constructor(privateIdentityKey) {
|
|
124
|
-
this.info = info + PrivateIdentityKey.version;
|
|
125
124
|
if (privateIdentityKey instanceof PrivateIdentityKeyConstructor) {
|
|
125
|
+
this.info = privateIdentityKey.info;
|
|
126
126
|
this.signatureKey = privateIdentityKey.signatureKey;
|
|
127
127
|
this.exchangeKey = privateIdentityKey.exchangeKey;
|
|
128
128
|
this.identityKey = privateIdentityKey.identityKey;
|
|
@@ -132,6 +132,7 @@ var PrivateIdentityKey;
|
|
|
132
132
|
privateIdentityKey = (0, utils_1.encodeBase64)(privateIdentityKey);
|
|
133
133
|
if (!isIdentityKeys(privateIdentityKey))
|
|
134
134
|
throw new Error("Invalid key length");
|
|
135
|
+
this.info = privateIdentityKey[0];
|
|
135
136
|
this.signatureKey = privateIdentityKey.subarray(1, crypto_1.default.EdDSA.secretKeyLength + 1);
|
|
136
137
|
this.exchangeKey = privateIdentityKey.subarray(crypto_1.default.EdDSA.secretKeyLength + 1, PrivateIdentityKey.keyLength);
|
|
137
138
|
this.identityKey = IdentityKey.from(crypto_1.default.EdDSA.keyPair(this.signatureKey).publicKey, crypto_1.default.ECDH.keyPair(this.exchangeKey).publicKey);
|
|
@@ -141,7 +142,7 @@ var PrivateIdentityKey;
|
|
|
141
142
|
return UserId.fromKey(this.identityKey.toBytes()).toString();
|
|
142
143
|
}
|
|
143
144
|
toBytes() {
|
|
144
|
-
return (0, utils_1.
|
|
145
|
+
return (0, utils_1.concatBytes)((0, utils_1.numberToBytes)(this.info, 1), this.signatureKey, this.exchangeKey);
|
|
145
146
|
}
|
|
146
147
|
toString() {
|
|
147
148
|
return (0, utils_1.decodeBase64)(this.toBytes());
|
|
@@ -163,10 +164,15 @@ var PrivateIdentityKey;
|
|
|
163
164
|
else
|
|
164
165
|
return key;
|
|
165
166
|
});
|
|
166
|
-
return new PrivateIdentityKeyConstructor(keys.length === 2 ? (0, utils_1.
|
|
167
|
+
return new PrivateIdentityKeyConstructor(keys.length === 2 ? (0, utils_1.concatBytes)((0, utils_1.numberToBytes)(info + PrivateIdentityKey.version, 1), ...keys) : keys[0]);
|
|
167
168
|
}
|
|
168
169
|
PrivateIdentityKey.from = from;
|
|
169
170
|
})(PrivateIdentityKey || (exports.PrivateIdentityKey = PrivateIdentityKey = {}));
|
|
171
|
+
var DiscoverType;
|
|
172
|
+
(function (DiscoverType) {
|
|
173
|
+
DiscoverType[DiscoverType["REQUEST"] = 0] = "REQUEST";
|
|
174
|
+
DiscoverType[DiscoverType["RESPONSE"] = 1] = "RESPONSE";
|
|
175
|
+
})(DiscoverType || (exports.DiscoverType = DiscoverType = {}));
|
|
170
176
|
var Protocols;
|
|
171
177
|
(function (Protocols) {
|
|
172
178
|
Protocols["NULL"] = "";
|
|
@@ -174,6 +180,7 @@ var Protocols;
|
|
|
174
180
|
Protocols["RELAY"] = "/freesignal/relay";
|
|
175
181
|
Protocols["HANDSHAKE"] = "/freesignal/handshake";
|
|
176
182
|
Protocols["DISCOVER"] = "/freesignal/discover";
|
|
183
|
+
Protocols["BOOTSTRAP"] = "/freesignal/bootstrap";
|
|
177
184
|
})(Protocols || (exports.Protocols = Protocols = {}));
|
|
178
185
|
(function (Protocols) {
|
|
179
186
|
function isProtocol(protocol) {
|
|
@@ -188,15 +195,16 @@ var Protocols;
|
|
|
188
195
|
return Object.values(Protocols).indexOf(protocol);
|
|
189
196
|
}
|
|
190
197
|
Protocols.toCode = toCode;
|
|
191
|
-
function encode(protocol, length) {
|
|
192
|
-
return (0, utils_1.
|
|
198
|
+
function encode(protocol, length = 1) {
|
|
199
|
+
return (0, utils_1.numberToBytes)(Protocols.toCode(protocol), length);
|
|
193
200
|
}
|
|
194
201
|
Protocols.encode = encode;
|
|
195
202
|
function decode(array) {
|
|
196
|
-
return Protocols.fromCode((0, utils_1.
|
|
203
|
+
return Protocols.fromCode((0, utils_1.bytesToNumber)(array));
|
|
197
204
|
}
|
|
198
205
|
Protocols.decode = decode;
|
|
199
206
|
})(Protocols || (exports.Protocols = Protocols = {}));
|
|
207
|
+
;
|
|
200
208
|
class Datagram {
|
|
201
209
|
constructor(sender, receiver, protocol, payload) {
|
|
202
210
|
this._id = crypto_1.default.UUID.generate().toString();
|
|
@@ -216,16 +224,6 @@ class Datagram {
|
|
|
216
224
|
get createdAt() {
|
|
217
225
|
return this._createdAt;
|
|
218
226
|
}
|
|
219
|
-
get signed() {
|
|
220
|
-
return !!this._signature || !!this.secretKey;
|
|
221
|
-
}
|
|
222
|
-
get signature() {
|
|
223
|
-
if (this.signed) {
|
|
224
|
-
if (!this._signature)
|
|
225
|
-
this.toBytes();
|
|
226
|
-
return (0, utils_1.decodeBase64)(this._signature);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
227
|
set payload(data) {
|
|
230
228
|
this._signature = undefined;
|
|
231
229
|
this._payload = data;
|
|
@@ -233,37 +231,56 @@ class Datagram {
|
|
|
233
231
|
get payload() {
|
|
234
232
|
return this._payload;
|
|
235
233
|
}
|
|
234
|
+
get signature() {
|
|
235
|
+
return this._signature ? (0, utils_1.decodeBase64)(this._signature) : undefined;
|
|
236
|
+
}
|
|
237
|
+
get unsigned() {
|
|
238
|
+
const data = this.toBytes();
|
|
239
|
+
data[0] &= 127;
|
|
240
|
+
return data.subarray(0, data.length - (this._signature ? crypto_1.default.EdDSA.signatureLength : 0));
|
|
241
|
+
}
|
|
236
242
|
toBytes() {
|
|
237
243
|
var _a, _b, _c;
|
|
238
|
-
|
|
244
|
+
return (0, utils_1.concatBytes)(new Uint8Array(1).fill(this.version | (this.signature ? 128 : 0)), //1
|
|
239
245
|
Protocols.encode(this.protocol), //1
|
|
240
246
|
(_a = crypto_1.default.UUID.parse(this.id)) !== null && _a !== void 0 ? _a : [], //16
|
|
241
|
-
(0, utils_1.
|
|
247
|
+
new Uint8Array((0, utils_1.numberToBytes)(this._createdAt, 8)), //8
|
|
242
248
|
(0, utils_1.encodeBase64)(this.sender), //32
|
|
243
249
|
(0, utils_1.encodeBase64)(this.receiver), //32
|
|
244
|
-
(_b = this._payload) !== null && _b !== void 0 ? _b : new Uint8Array());
|
|
245
|
-
if (this.secretKey)
|
|
246
|
-
this._signature = crypto_1.default.EdDSA.sign(data, this.secretKey);
|
|
247
|
-
return (0, utils_1.concatArrays)(data, (_c = this._signature) !== null && _c !== void 0 ? _c : new Uint8Array());
|
|
250
|
+
(_b = this._payload) !== null && _b !== void 0 ? _b : new Uint8Array(), (_c = this._signature) !== null && _c !== void 0 ? _c : new Uint8Array());
|
|
248
251
|
}
|
|
249
252
|
sign(secretKey) {
|
|
250
|
-
this.
|
|
253
|
+
this._signature = crypto_1.default.EdDSA.sign(this.unsigned, secretKey);
|
|
251
254
|
return this;
|
|
252
255
|
}
|
|
253
256
|
toString() {
|
|
254
257
|
return (0, utils_1.decodeBase64)(this.toBytes());
|
|
255
258
|
}
|
|
256
259
|
toJSON() {
|
|
257
|
-
return
|
|
260
|
+
return {
|
|
261
|
+
id: this.id,
|
|
262
|
+
version: this.version,
|
|
263
|
+
sender: this.sender,
|
|
264
|
+
receiver: this.receiver,
|
|
265
|
+
protocol: this.protocol,
|
|
266
|
+
createdAt: this.createdAt,
|
|
267
|
+
payload: this.payload ? (0, utils_1.decodeBase64)(this.payload) : undefined,
|
|
268
|
+
signature: this._signature ? (0, utils_1.decodeBase64)(this._signature) : undefined
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
static verify(datagram, publicKey) {
|
|
272
|
+
if (!datagram._signature)
|
|
273
|
+
throw new Error("Datagram not signed");
|
|
274
|
+
return crypto_1.default.EdDSA.verify(datagram.unsigned, datagram._signature, publicKey);
|
|
258
275
|
}
|
|
259
276
|
static from(data) {
|
|
260
277
|
if (typeof data === 'string')
|
|
261
278
|
data = (0, utils_1.encodeBase64)(data);
|
|
262
279
|
if (data instanceof Uint8Array) {
|
|
263
|
-
const datagram = new Datagram((0, utils_1.decodeBase64)(data.subarray(26, 26 + crypto_1.default.EdDSA.publicKeyLength)), (0, utils_1.decodeBase64)(data.subarray(26 + crypto_1.default.EdDSA.publicKeyLength, Datagram.headerOffset)), Protocols.decode(data.subarray(1, 2)), data.subarray(Datagram.headerOffset, data.length));
|
|
280
|
+
const datagram = new Datagram((0, utils_1.decodeBase64)(data.subarray(26, 26 + crypto_1.default.EdDSA.publicKeyLength)), (0, utils_1.decodeBase64)(data.subarray(26 + crypto_1.default.EdDSA.publicKeyLength, Datagram.headerOffset)), Protocols.decode(data.subarray(1, 2)), data.subarray(Datagram.headerOffset, data.length - (data[0] & 128 ? crypto_1.default.EdDSA.signatureLength : 0)));
|
|
264
281
|
datagram._version = data[0] & 127;
|
|
265
282
|
datagram._id = crypto_1.default.UUID.stringify(data.subarray(2, 18));
|
|
266
|
-
datagram._createdAt = (0, utils_1.
|
|
283
|
+
datagram._createdAt = (0, utils_1.bytesToNumber)(data.subarray(18, 26));
|
|
267
284
|
if (data[0] & 128)
|
|
268
285
|
datagram._signature = data.subarray(data.length - crypto_1.default.EdDSA.signatureLength);
|
|
269
286
|
return datagram;
|
|
@@ -272,8 +289,8 @@ class Datagram {
|
|
|
272
289
|
const datagram = new Datagram(data.sender, data.receiver, data.protocol, data.payload);
|
|
273
290
|
datagram._id = datagram.id;
|
|
274
291
|
datagram._version = datagram.version;
|
|
275
|
-
datagram._createdAt = datagram.
|
|
276
|
-
datagram._signature = datagram.
|
|
292
|
+
datagram._createdAt = datagram._createdAt;
|
|
293
|
+
datagram._signature = datagram._signature;
|
|
277
294
|
return datagram;
|
|
278
295
|
}
|
|
279
296
|
else
|
|
@@ -39,7 +39,7 @@ export declare class KeyExchange {
|
|
|
39
39
|
private generateOPK;
|
|
40
40
|
generateBundle(length?: number): Promise<KeyExchangeDataBundle>;
|
|
41
41
|
generateData(): Promise<KeyExchangeData>;
|
|
42
|
-
digestData(message: KeyExchangeData): Promise<{
|
|
42
|
+
digestData(message: KeyExchangeData, associatedData?: Uint8Array): Promise<{
|
|
43
43
|
session: KeySession;
|
|
44
44
|
message: KeyExchangeSynMessage;
|
|
45
45
|
identityKey: IdentityKey;
|
|
@@ -47,5 +47,6 @@ export declare class KeyExchange {
|
|
|
47
47
|
digestMessage(message: KeyExchangeSynMessage): Promise<{
|
|
48
48
|
session: KeySession;
|
|
49
49
|
identityKey: IdentityKey;
|
|
50
|
+
associatedData: Uint8Array;
|
|
50
51
|
}>;
|
|
51
52
|
}
|
package/{x3dh.js → dist/x3dh.js}
RENAMED
|
@@ -66,7 +66,7 @@ class KeyExchange {
|
|
|
66
66
|
identityKey: this.identityKey.toString(),
|
|
67
67
|
signedPreKey: (0, utils_1.decodeBase64)(signedPreKey.publicKey),
|
|
68
68
|
signature: (0, utils_1.decodeBase64)(crypto_1.default.EdDSA.sign(signedPreKeyHash, this.privateIdentityKey.signatureKey)),
|
|
69
|
-
|
|
69
|
+
onetimePreKeys: onetimePreKey.map(opk => (0, utils_1.decodeBase64)(opk.publicKey))
|
|
70
70
|
};
|
|
71
71
|
});
|
|
72
72
|
}
|
|
@@ -83,7 +83,7 @@ class KeyExchange {
|
|
|
83
83
|
};
|
|
84
84
|
});
|
|
85
85
|
}
|
|
86
|
-
digestData(message) {
|
|
86
|
+
digestData(message, associatedData) {
|
|
87
87
|
return __awaiter(this, void 0, void 0, function* () {
|
|
88
88
|
const ephemeralKey = crypto_1.default.ECDH.keyPair();
|
|
89
89
|
const signedPreKey = (0, utils_1.encodeBase64)(message.signedPreKey);
|
|
@@ -98,9 +98,9 @@ class KeyExchange {
|
|
|
98
98
|
...crypto_1.default.ECDH.scalarMult(ephemeralKey.secretKey, identityKey.exchangeKey),
|
|
99
99
|
...crypto_1.default.ECDH.scalarMult(ephemeralKey.secretKey, signedPreKey),
|
|
100
100
|
...onetimePreKey ? crypto_1.default.ECDH.scalarMult(ephemeralKey.secretKey, onetimePreKey) : new Uint8Array()
|
|
101
|
-
]), new Uint8Array(double_ratchet_1.KeySession.
|
|
101
|
+
]), new Uint8Array(double_ratchet_1.KeySession.keyLength).fill(0), KeyExchange.hkdfInfo, double_ratchet_1.KeySession.keyLength);
|
|
102
102
|
const session = new double_ratchet_1.KeySession(this.sessions, { remoteKey: identityKey.exchangeKey, rootKey });
|
|
103
|
-
const cyphertext = yield session.encrypt((0, utils_1.
|
|
103
|
+
const cyphertext = yield session.encrypt((0, utils_1.concatBytes)(crypto_1.default.hash(this.identityKey.toBytes()), crypto_1.default.hash(identityKey.toBytes()), associatedData !== null && associatedData !== void 0 ? associatedData : new Uint8Array()));
|
|
104
104
|
if (!cyphertext)
|
|
105
105
|
throw new Error("Decryption error");
|
|
106
106
|
return {
|
|
@@ -133,21 +133,22 @@ class KeyExchange {
|
|
|
133
133
|
...crypto_1.default.ECDH.scalarMult(this.privateIdentityKey.exchangeKey, ephemeralKey),
|
|
134
134
|
...crypto_1.default.ECDH.scalarMult(signedPreKey.secretKey, ephemeralKey),
|
|
135
135
|
...onetimePreKey ? crypto_1.default.ECDH.scalarMult(onetimePreKey.secretKey, ephemeralKey) : new Uint8Array()
|
|
136
|
-
]), new Uint8Array(double_ratchet_1.KeySession.
|
|
136
|
+
]), new Uint8Array(double_ratchet_1.KeySession.keyLength).fill(0), KeyExchange.hkdfInfo, double_ratchet_1.KeySession.keyLength);
|
|
137
137
|
const session = new double_ratchet_1.KeySession(this.sessions, { secretKey: this.privateIdentityKey.exchangeKey, rootKey });
|
|
138
138
|
const cleartext = yield session.decrypt((0, utils_1.encodeBase64)(message.associatedData));
|
|
139
139
|
if (!cleartext)
|
|
140
140
|
throw new Error("Error decrypting ACK message");
|
|
141
|
-
if (!(0, utils_1.
|
|
141
|
+
if (!(0, utils_1.compareBytes)(cleartext.subarray(0, 64), (0, utils_1.concatBytes)(crypto_1.default.hash(identityKey.toBytes()), crypto_1.default.hash(this.identityKey.toBytes()))))
|
|
142
142
|
throw new Error("Error verifing Associated Data");
|
|
143
143
|
return {
|
|
144
144
|
session,
|
|
145
|
-
identityKey
|
|
145
|
+
identityKey,
|
|
146
|
+
associatedData: cleartext.subarray(64)
|
|
146
147
|
};
|
|
147
148
|
});
|
|
148
149
|
}
|
|
149
150
|
}
|
|
150
151
|
exports.KeyExchange = KeyExchange;
|
|
151
152
|
KeyExchange.version = 1;
|
|
152
|
-
KeyExchange.hkdfInfo =
|
|
153
|
+
KeyExchange.hkdfInfo = "freesignal/x3dh/" + KeyExchange.version;
|
|
153
154
|
KeyExchange.maxOPK = 10;
|