@freesignal/protocol 0.2.9 → 0.2.11
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 +37 -4
- package/double-ratchet.js +133 -39
- package/index.d.ts +12 -15
- package/index.js +31 -20
- package/node.d.ts +27 -0
- package/node.js +105 -0
- package/package.json +3 -8
- package/test.js +21 -26
- package/types.d.ts +66 -136
- package/types.js +238 -366
- package/x3dh.d.ts +18 -19
- package/x3dh.js +75 -114
- package/api.d.ts +0 -52
- package/api.js +0 -194
package/double-ratchet.d.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* You should have received a copy of the GNU General Public License
|
|
17
17
|
* along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
18
18
|
*/
|
|
19
|
+
import { LocalStorage } from "@freesignal/interfaces";
|
|
19
20
|
import { EncryptedData } from "./types";
|
|
20
21
|
export interface ExportedKeySession {
|
|
21
22
|
secretKey: string;
|
|
@@ -36,6 +37,7 @@ export declare class KeySession {
|
|
|
36
37
|
private static readonly skipLimit;
|
|
37
38
|
static readonly version = 1;
|
|
38
39
|
static readonly rootKeyLength: number;
|
|
40
|
+
readonly id: string;
|
|
39
41
|
private keyPair;
|
|
40
42
|
private _remoteKey?;
|
|
41
43
|
private rootKey?;
|
|
@@ -45,7 +47,9 @@ export declare class KeySession {
|
|
|
45
47
|
private receivingChain?;
|
|
46
48
|
private receivingCount;
|
|
47
49
|
private previousKeys;
|
|
48
|
-
|
|
50
|
+
private readonly storage;
|
|
51
|
+
constructor(storage: LocalStorage<string, ExportedKeySession>, opts?: {
|
|
52
|
+
id?: string;
|
|
49
53
|
secretKey?: Uint8Array;
|
|
50
54
|
remoteKey?: Uint8Array;
|
|
51
55
|
rootKey?: Uint8Array;
|
|
@@ -66,20 +70,21 @@ export declare class KeySession {
|
|
|
66
70
|
private ratchetKeys;
|
|
67
71
|
private getSendingKey;
|
|
68
72
|
private getReceivingKey;
|
|
73
|
+
private save;
|
|
69
74
|
/**
|
|
70
75
|
* Encrypts a message payload using the current sending chain.
|
|
71
76
|
*
|
|
72
77
|
* @param message - The message as a Uint8Array.
|
|
73
78
|
* @returns An EncryptedPayload or undefined if encryption fails.
|
|
74
79
|
*/
|
|
75
|
-
encrypt(message: Uint8Array): EncryptedData
|
|
80
|
+
encrypt(message: Uint8Array): Promise<EncryptedData>;
|
|
76
81
|
/**
|
|
77
82
|
* Decrypts an encrypted message.
|
|
78
83
|
*
|
|
79
84
|
* @param payload - The received encrypted message.
|
|
80
85
|
* @returns The decrypted message as a Uint8Array, or undefined if decryption fails.
|
|
81
86
|
*/
|
|
82
|
-
decrypt(payload: Uint8Array | EncryptedData): Uint8Array | undefined
|
|
87
|
+
decrypt(payload: Uint8Array | EncryptedData): Promise<Uint8Array | undefined>;
|
|
83
88
|
/**
|
|
84
89
|
* Export the state of the session;
|
|
85
90
|
*/
|
|
@@ -90,7 +95,7 @@ export declare class KeySession {
|
|
|
90
95
|
* @param json string returned by `export()` method.
|
|
91
96
|
* @returns session with the state parsed.
|
|
92
97
|
*/
|
|
93
|
-
static from(data: ExportedKeySession): KeySession;
|
|
98
|
+
static from(data: ExportedKeySession, storage: LocalStorage<string, ExportedKeySession>): KeySession;
|
|
94
99
|
/**
|
|
95
100
|
* The fixed key length (in bytes) used throughout the Double Ratchet session.
|
|
96
101
|
* Typically 32 bytes (256 bits) for symmetric keys.
|
|
@@ -98,3 +103,31 @@ export declare class KeySession {
|
|
|
98
103
|
static readonly keyLength = 32;
|
|
99
104
|
private static symmetricRatchet;
|
|
100
105
|
}
|
|
106
|
+
export declare class EncryptedDataConstructor implements EncryptedData {
|
|
107
|
+
static readonly secretKeyLength: number;
|
|
108
|
+
static readonly publicKeyLength: number;
|
|
109
|
+
static readonly keyLength: number;
|
|
110
|
+
static readonly nonceLength: number;
|
|
111
|
+
static readonly maxCount = 65536;
|
|
112
|
+
static readonly countLength = 2;
|
|
113
|
+
private raw;
|
|
114
|
+
constructor(count: number | Uint8Array, previous: number | Uint8Array, publicKey: Uint8Array, nonce: Uint8Array, ciphertext: Uint8Array, version?: number | Uint8Array);
|
|
115
|
+
constructor(encrypted: Uint8Array | EncryptedData);
|
|
116
|
+
get length(): number;
|
|
117
|
+
get version(): number;
|
|
118
|
+
get count(): number;
|
|
119
|
+
get previous(): number;
|
|
120
|
+
get publicKey(): Uint8Array;
|
|
121
|
+
get nonce(): Uint8Array;
|
|
122
|
+
get ciphertext(): Uint8Array;
|
|
123
|
+
toBytes(): Uint8Array;
|
|
124
|
+
toString(): string;
|
|
125
|
+
toJSON(): {
|
|
126
|
+
version: number;
|
|
127
|
+
count: number;
|
|
128
|
+
previous: number;
|
|
129
|
+
publicKey: string;
|
|
130
|
+
nonce: string;
|
|
131
|
+
ciphertext: string;
|
|
132
|
+
};
|
|
133
|
+
}
|
package/double-ratchet.js
CHANGED
|
@@ -17,11 +17,20 @@
|
|
|
17
17
|
* You should have received a copy of the GNU General Public License
|
|
18
18
|
* along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
19
19
|
*/
|
|
20
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
21
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
22
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
23
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
24
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
25
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
26
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
27
|
+
});
|
|
28
|
+
};
|
|
20
29
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
21
30
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
22
31
|
};
|
|
23
32
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
-
exports.KeySession = void 0;
|
|
33
|
+
exports.EncryptedDataConstructor = exports.KeySession = void 0;
|
|
25
34
|
const crypto_1 = __importDefault(require("@freesignal/crypto"));
|
|
26
35
|
const utils_1 = require("@freesignal/utils");
|
|
27
36
|
const types_1 = require("./types");
|
|
@@ -30,11 +39,13 @@ const types_1 = require("./types");
|
|
|
30
39
|
* Used for forward-secure encryption and decryption of messages.
|
|
31
40
|
*/
|
|
32
41
|
class KeySession {
|
|
33
|
-
constructor(opts = {}) {
|
|
42
|
+
constructor(storage, opts = {}) {
|
|
43
|
+
var _a;
|
|
34
44
|
this.sendingCount = 0;
|
|
35
45
|
this.previousCount = 0;
|
|
36
46
|
this.receivingCount = 0;
|
|
37
47
|
this.previousKeys = new KeyMap();
|
|
48
|
+
this.id = (_a = opts.id) !== null && _a !== void 0 ? _a : crypto_1.default.UUID.generate().toString();
|
|
38
49
|
this.keyPair = crypto_1.default.ECDH.keyPair(opts.secretKey);
|
|
39
50
|
if (opts.rootKey)
|
|
40
51
|
this.rootKey = opts.rootKey;
|
|
@@ -42,6 +53,8 @@ class KeySession {
|
|
|
42
53
|
this._remoteKey = opts.remoteKey;
|
|
43
54
|
this.sendingChain = this.ratchetKeys();
|
|
44
55
|
}
|
|
56
|
+
this.storage = storage;
|
|
57
|
+
this.storage.set(this.id, this.toJSON());
|
|
45
58
|
}
|
|
46
59
|
/**
|
|
47
60
|
* Whether both the sending and receiving chains are initialized.
|
|
@@ -58,12 +71,12 @@ class KeySession {
|
|
|
58
71
|
setRemoteKey(key) {
|
|
59
72
|
this._remoteKey = key;
|
|
60
73
|
this.receivingChain = this.ratchetKeys();
|
|
61
|
-
if (this.receivingCount > (
|
|
74
|
+
if (this.receivingCount > (EncryptedDataConstructor.maxCount - KeySession.skipLimit * 2))
|
|
62
75
|
this.receivingCount = 0;
|
|
63
76
|
this.previousCount = this.sendingCount;
|
|
64
77
|
this.keyPair = crypto_1.default.ECDH.keyPair();
|
|
65
78
|
this.sendingChain = this.ratchetKeys();
|
|
66
|
-
if (this.sendingCount > (
|
|
79
|
+
if (this.sendingCount > (EncryptedDataConstructor.maxCount - KeySession.skipLimit * 2))
|
|
67
80
|
this.sendingCount = 0;
|
|
68
81
|
return this;
|
|
69
82
|
}
|
|
@@ -93,6 +106,9 @@ class KeySession {
|
|
|
93
106
|
this.receivingCount++;
|
|
94
107
|
return sharedKey;
|
|
95
108
|
}
|
|
109
|
+
save() {
|
|
110
|
+
return this.storage.set(this.id, this.toJSON());
|
|
111
|
+
}
|
|
96
112
|
/**
|
|
97
113
|
* Encrypts a message payload using the current sending chain.
|
|
98
114
|
*
|
|
@@ -100,12 +116,15 @@ class KeySession {
|
|
|
100
116
|
* @returns An EncryptedPayload or undefined if encryption fails.
|
|
101
117
|
*/
|
|
102
118
|
encrypt(message) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
119
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
120
|
+
const key = this.getSendingKey();
|
|
121
|
+
if (this.sendingCount >= EncryptedDataConstructor.maxCount || this.previousCount >= EncryptedDataConstructor.maxCount)
|
|
122
|
+
throw new Error();
|
|
123
|
+
const nonce = crypto_1.default.randomBytes(EncryptedDataConstructor.nonceLength);
|
|
124
|
+
const ciphertext = crypto_1.default.box.encrypt(message, nonce, key);
|
|
125
|
+
yield this.save();
|
|
126
|
+
return new EncryptedDataConstructor(this.sendingCount, this.previousCount, this.keyPair.publicKey, nonce, ciphertext);
|
|
127
|
+
});
|
|
109
128
|
}
|
|
110
129
|
/**
|
|
111
130
|
* Decrypts an encrypted message.
|
|
@@ -114,42 +133,42 @@ class KeySession {
|
|
|
114
133
|
* @returns The decrypted message as a Uint8Array, or undefined if decryption fails.
|
|
115
134
|
*/
|
|
116
135
|
decrypt(payload) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
while (this.receivingCount < encrypted.previous)
|
|
124
|
-
this.previousKeys.set(this.receivingCount, this.getReceivingKey());
|
|
136
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
137
|
+
const encrypted = types_1.EncryptedData.from(payload);
|
|
138
|
+
const publicKey = encrypted.publicKey;
|
|
139
|
+
if (this._remoteKey && !(0, utils_1.verifyArrays)(publicKey, this._remoteKey))
|
|
140
|
+
while (this.receivingCount < encrypted.previous)
|
|
141
|
+
this.previousKeys.set(this.receivingCount, this.getReceivingKey());
|
|
125
142
|
this.setRemoteKey(publicKey);
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
143
|
+
let key;
|
|
144
|
+
const count = encrypted.count;
|
|
145
|
+
if (this.receivingCount < count) {
|
|
146
|
+
let i = 0;
|
|
147
|
+
while (this.receivingCount < count - 1 && i < KeySession.skipLimit) {
|
|
148
|
+
this.previousKeys.set(this.receivingCount, this.getReceivingKey());
|
|
149
|
+
}
|
|
150
|
+
key = this.getReceivingKey();
|
|
133
151
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return
|
|
141
|
-
|
|
152
|
+
else {
|
|
153
|
+
key = this.previousKeys.get(count);
|
|
154
|
+
}
|
|
155
|
+
if (!key)
|
|
156
|
+
return undefined;
|
|
157
|
+
yield this.save();
|
|
158
|
+
return crypto_1.default.box.decrypt(encrypted.ciphertext, encrypted.nonce, key);
|
|
159
|
+
});
|
|
142
160
|
}
|
|
143
161
|
/**
|
|
144
162
|
* Export the state of the session;
|
|
145
163
|
*/
|
|
146
164
|
toJSON() {
|
|
165
|
+
var _a, _b, _c, _d;
|
|
147
166
|
return {
|
|
148
167
|
secretKey: (0, utils_1.decodeBase64)((0, utils_1.concatArrays)(this.keyPair.secretKey)),
|
|
149
|
-
remoteKey: (0, utils_1.decodeBase64)(this._remoteKey),
|
|
150
|
-
rootKey: (0, utils_1.decodeBase64)(this.rootKey),
|
|
151
|
-
sendingChain: (0, utils_1.decodeBase64)(this.sendingChain),
|
|
152
|
-
receivingChain: (0, utils_1.decodeBase64)(this.receivingChain),
|
|
168
|
+
remoteKey: (0, utils_1.decodeBase64)((_a = this._remoteKey) !== null && _a !== void 0 ? _a : new Uint8Array()),
|
|
169
|
+
rootKey: (0, utils_1.decodeBase64)((_b = this.rootKey) !== null && _b !== void 0 ? _b : new Uint8Array()),
|
|
170
|
+
sendingChain: (0, utils_1.decodeBase64)((_c = this.sendingChain) !== null && _c !== void 0 ? _c : new Uint8Array()),
|
|
171
|
+
receivingChain: (0, utils_1.decodeBase64)((_d = this.receivingChain) !== null && _d !== void 0 ? _d : new Uint8Array()),
|
|
153
172
|
sendingCount: this.sendingCount,
|
|
154
173
|
receivingCount: this.receivingCount,
|
|
155
174
|
previousCount: this.previousCount,
|
|
@@ -162,8 +181,8 @@ class KeySession {
|
|
|
162
181
|
* @param json string returned by `export()` method.
|
|
163
182
|
* @returns session with the state parsed.
|
|
164
183
|
*/
|
|
165
|
-
static from(data) {
|
|
166
|
-
const session = new KeySession({ secretKey: (0, utils_1.encodeBase64)(data.secretKey), rootKey: (0, utils_1.encodeBase64)(data.rootKey) });
|
|
184
|
+
static from(data, storage) {
|
|
185
|
+
const session = new KeySession(storage, { secretKey: (0, utils_1.encodeBase64)(data.secretKey), rootKey: (0, utils_1.encodeBase64)(data.rootKey) });
|
|
167
186
|
session._remoteKey = (0, utils_1.encodeBase64)(data.remoteKey);
|
|
168
187
|
session.sendingChain = (0, utils_1.encodeBase64)(data.sendingChain);
|
|
169
188
|
session.receivingChain = (0, utils_1.encodeBase64)(data.receivingChain);
|
|
@@ -171,6 +190,7 @@ class KeySession {
|
|
|
171
190
|
session.receivingCount = data.receivingCount;
|
|
172
191
|
session.previousCount = data.previousCount;
|
|
173
192
|
session.previousKeys = new KeyMap(data.previousKeys);
|
|
193
|
+
session.save();
|
|
174
194
|
return session;
|
|
175
195
|
}
|
|
176
196
|
static symmetricRatchet(chain, salt, info) {
|
|
@@ -190,6 +210,80 @@ KeySession.rootKeyLength = crypto_1.default.box.keyLength;
|
|
|
190
210
|
* Typically 32 bytes (256 bits) for symmetric keys.
|
|
191
211
|
*/
|
|
192
212
|
KeySession.keyLength = 32;
|
|
213
|
+
class EncryptedDataConstructor {
|
|
214
|
+
constructor(...arrays) {
|
|
215
|
+
arrays = arrays.filter(value => value !== undefined);
|
|
216
|
+
if (arrays[0] instanceof EncryptedDataConstructor) {
|
|
217
|
+
this.raw = arrays[0].raw;
|
|
218
|
+
return this;
|
|
219
|
+
}
|
|
220
|
+
if (typeof arrays[0] === 'number')
|
|
221
|
+
arrays[0] = (0, utils_1.numberToArray)(arrays[0], EncryptedDataConstructor.countLength);
|
|
222
|
+
if (typeof arrays[1] === 'number')
|
|
223
|
+
arrays[1] = (0, utils_1.numberToArray)(arrays[1], EncryptedDataConstructor.countLength);
|
|
224
|
+
if (arrays.length === 6) {
|
|
225
|
+
arrays.unshift(typeof arrays[5] === 'number' ? (0, utils_1.numberToArray)(arrays[5]) : arrays[5]);
|
|
226
|
+
arrays.pop();
|
|
227
|
+
}
|
|
228
|
+
else if (arrays.length > 1) {
|
|
229
|
+
arrays.unshift((0, utils_1.numberToArray)(KeySession.version));
|
|
230
|
+
}
|
|
231
|
+
this.raw = (0, utils_1.concatArrays)(...arrays);
|
|
232
|
+
}
|
|
233
|
+
get length() { return this.raw.length; }
|
|
234
|
+
get version() { return (0, utils_1.numberFromArray)(new Uint8Array(this.raw.buffer, ...Offsets.version.get)); }
|
|
235
|
+
get count() { return (0, utils_1.numberFromArray)(new Uint8Array(this.raw.buffer, ...Offsets.count.get)); }
|
|
236
|
+
get previous() { return (0, utils_1.numberFromArray)(new Uint8Array(this.raw.buffer, ...Offsets.previous.get)); }
|
|
237
|
+
get publicKey() { return new Uint8Array(this.raw.buffer, ...Offsets.publicKey.get); }
|
|
238
|
+
get nonce() { return new Uint8Array(this.raw.buffer, ...Offsets.nonce.get); }
|
|
239
|
+
get ciphertext() { return new Uint8Array(this.raw.buffer, Offsets.ciphertext.start); }
|
|
240
|
+
toBytes() {
|
|
241
|
+
return this.raw;
|
|
242
|
+
}
|
|
243
|
+
toString() {
|
|
244
|
+
return (0, utils_1.decodeBase64)(this.raw);
|
|
245
|
+
}
|
|
246
|
+
toJSON() {
|
|
247
|
+
return {
|
|
248
|
+
version: this.version,
|
|
249
|
+
count: this.count,
|
|
250
|
+
previous: this.previous,
|
|
251
|
+
publicKey: (0, utils_1.decodeBase64)(this.publicKey),
|
|
252
|
+
nonce: (0, utils_1.decodeBase64)(this.nonce),
|
|
253
|
+
ciphertext: (0, utils_1.decodeBase64)(this.ciphertext)
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
exports.EncryptedDataConstructor = EncryptedDataConstructor;
|
|
258
|
+
EncryptedDataConstructor.secretKeyLength = crypto_1.default.ECDH.secretKeyLength;
|
|
259
|
+
EncryptedDataConstructor.publicKeyLength = crypto_1.default.ECDH.publicKeyLength;
|
|
260
|
+
EncryptedDataConstructor.keyLength = crypto_1.default.box.keyLength;
|
|
261
|
+
EncryptedDataConstructor.nonceLength = crypto_1.default.box.nonceLength;
|
|
262
|
+
EncryptedDataConstructor.maxCount = 65536; //32768;
|
|
263
|
+
EncryptedDataConstructor.countLength = 2;
|
|
264
|
+
class Offsets {
|
|
265
|
+
static set(start, length) {
|
|
266
|
+
class Offset {
|
|
267
|
+
constructor(start, length) {
|
|
268
|
+
this.start = start;
|
|
269
|
+
this.length = length;
|
|
270
|
+
if (typeof length === 'number')
|
|
271
|
+
this.end = start + length;
|
|
272
|
+
}
|
|
273
|
+
get get() {
|
|
274
|
+
return [this.start, this.length];
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return new Offset(start, length);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
Offsets.checksum = Offsets.set(0, 0);
|
|
281
|
+
Offsets.version = Offsets.set(Offsets.checksum.end, 1);
|
|
282
|
+
Offsets.count = Offsets.set(Offsets.version.end, EncryptedDataConstructor.countLength);
|
|
283
|
+
Offsets.previous = Offsets.set(Offsets.count.end, EncryptedDataConstructor.countLength);
|
|
284
|
+
Offsets.publicKey = Offsets.set(Offsets.previous.end, EncryptedDataConstructor.publicKeyLength);
|
|
285
|
+
Offsets.nonce = Offsets.set(Offsets.publicKey.end, EncryptedDataConstructor.nonceLength);
|
|
286
|
+
Offsets.ciphertext = Offsets.set(Offsets.nonce.end, undefined);
|
|
193
287
|
class KeyMap extends Map {
|
|
194
288
|
get(key) {
|
|
195
289
|
const out = super.get(key);
|
package/index.d.ts
CHANGED
|
@@ -16,10 +16,10 @@
|
|
|
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 crypto from "@freesignal/crypto";
|
|
20
19
|
import { LocalStorage, Crypto } from "@freesignal/interfaces";
|
|
21
|
-
import { KeySession } from "./double-ratchet";
|
|
20
|
+
import { ExportedKeySession, KeySession } from "./double-ratchet";
|
|
22
21
|
import { KeyExchange } from "./x3dh";
|
|
22
|
+
import { PrivateIdentityKey } from "./types";
|
|
23
23
|
/**
|
|
24
24
|
* Creates a new Double Ratchet session for secure message exchange.
|
|
25
25
|
*
|
|
@@ -29,7 +29,7 @@ import { KeyExchange } from "./x3dh";
|
|
|
29
29
|
* @param opts.rootKey - An optional root key to initialize the session.
|
|
30
30
|
* @returns A new instance of {@link KeySession}.
|
|
31
31
|
*/
|
|
32
|
-
export declare function createKeySession(opts?: {
|
|
32
|
+
export declare function createKeySession(storage: LocalStorage<string, ExportedKeySession>, opts?: {
|
|
33
33
|
secretKey?: Uint8Array;
|
|
34
34
|
remoteKey?: Uint8Array;
|
|
35
35
|
rootKey?: Uint8Array;
|
|
@@ -37,21 +37,18 @@ export declare function createKeySession(opts?: {
|
|
|
37
37
|
/**
|
|
38
38
|
* Creates a new X3DH (Extended Triple Diffie-Hellman) key exchange session.
|
|
39
39
|
*
|
|
40
|
-
* @param
|
|
41
|
-
* @param boxSecretKey - The ECDH box secret key as a Uint8Array.
|
|
42
|
-
* @param bundleStore - Optional local storage for key bundles.
|
|
40
|
+
* @param storage - Local storage for keys.
|
|
43
41
|
* @returns A new instance of {@link KeyExchange}.
|
|
44
42
|
*/
|
|
45
|
-
export declare function createKeyExchange(
|
|
43
|
+
export declare function createKeyExchange(storage: {
|
|
44
|
+
keys: LocalStorage<string, Crypto.KeyPair>;
|
|
45
|
+
sessions: LocalStorage<string, ExportedKeySession>;
|
|
46
|
+
}, privateIdentityKey?: PrivateIdentityKey): KeyExchange;
|
|
46
47
|
/**
|
|
47
|
-
* Generates identity key
|
|
48
|
+
* Generates identity key
|
|
48
49
|
*
|
|
49
|
-
* @param
|
|
50
|
-
* @param boxSecretKey - Optional secret key for ECDH encryption.
|
|
50
|
+
* @param seed - Seed to generate the key.
|
|
51
51
|
* @returns An object containing readonly signing and box key pairs.
|
|
52
52
|
*/
|
|
53
|
-
export declare function
|
|
54
|
-
|
|
55
|
-
readonly box: Crypto.KeyPair;
|
|
56
|
-
};
|
|
57
|
-
export { UserId, IdentityKeys, Protocols, Datagram, EncryptedData } from "./types";
|
|
53
|
+
export declare function createIdentity(seed?: Uint8Array): PrivateIdentityKey;
|
|
54
|
+
export * from "./types";
|
package/index.js
CHANGED
|
@@ -17,17 +17,31 @@
|
|
|
17
17
|
* You should have received a copy of the GNU General Public License
|
|
18
18
|
* along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
19
19
|
*/
|
|
20
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
23
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
24
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
25
|
+
}
|
|
26
|
+
Object.defineProperty(o, k2, desc);
|
|
27
|
+
}) : (function(o, m, k, k2) {
|
|
28
|
+
if (k2 === undefined) k2 = k;
|
|
29
|
+
o[k2] = m[k];
|
|
30
|
+
}));
|
|
31
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
32
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
33
|
+
};
|
|
20
34
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
21
35
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
22
36
|
};
|
|
23
37
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
-
exports.EncryptedData = exports.Datagram = exports.Protocols = exports.IdentityKeys = exports.UserId = void 0;
|
|
25
38
|
exports.createKeySession = createKeySession;
|
|
26
39
|
exports.createKeyExchange = createKeyExchange;
|
|
27
|
-
exports.
|
|
40
|
+
exports.createIdentity = createIdentity;
|
|
28
41
|
const crypto_1 = __importDefault(require("@freesignal/crypto"));
|
|
29
42
|
const double_ratchet_1 = require("./double-ratchet");
|
|
30
43
|
const x3dh_1 = require("./x3dh");
|
|
44
|
+
const types_1 = require("./types");
|
|
31
45
|
/**
|
|
32
46
|
* Creates a new Double Ratchet session for secure message exchange.
|
|
33
47
|
*
|
|
@@ -37,33 +51,30 @@ const x3dh_1 = require("./x3dh");
|
|
|
37
51
|
* @param opts.rootKey - An optional root key to initialize the session.
|
|
38
52
|
* @returns A new instance of {@link KeySession}.
|
|
39
53
|
*/
|
|
40
|
-
function createKeySession(opts) {
|
|
41
|
-
return new double_ratchet_1.KeySession(opts);
|
|
54
|
+
function createKeySession(storage, opts) {
|
|
55
|
+
return new double_ratchet_1.KeySession(storage, opts);
|
|
42
56
|
}
|
|
43
57
|
/**
|
|
44
58
|
* Creates a new X3DH (Extended Triple Diffie-Hellman) key exchange session.
|
|
45
59
|
*
|
|
46
|
-
* @param
|
|
47
|
-
* @param boxSecretKey - The ECDH box secret key as a Uint8Array.
|
|
48
|
-
* @param bundleStore - Optional local storage for key bundles.
|
|
60
|
+
* @param storage - Local storage for keys.
|
|
49
61
|
* @returns A new instance of {@link KeyExchange}.
|
|
50
62
|
*/
|
|
51
|
-
function createKeyExchange(
|
|
52
|
-
return new x3dh_1.KeyExchange(
|
|
63
|
+
function createKeyExchange(storage, privateIdentityKey) {
|
|
64
|
+
return new x3dh_1.KeyExchange(storage, privateIdentityKey);
|
|
53
65
|
}
|
|
54
66
|
/**
|
|
55
|
-
* Generates identity key
|
|
67
|
+
* Generates identity key
|
|
56
68
|
*
|
|
57
|
-
* @param
|
|
58
|
-
* @param boxSecretKey - Optional secret key for ECDH encryption.
|
|
69
|
+
* @param seed - Seed to generate the key.
|
|
59
70
|
* @returns An object containing readonly signing and box key pairs.
|
|
60
71
|
*/
|
|
61
|
-
function
|
|
62
|
-
|
|
72
|
+
function createIdentity(seed) {
|
|
73
|
+
seed !== null && seed !== void 0 ? seed : (seed = crypto_1.default.randomBytes(crypto_1.default.EdDSA.seedLength));
|
|
74
|
+
const signatureSeed = crypto_1.default.hkdf(seed, new Uint8Array(crypto_1.default.EdDSA.seedLength).fill(0), "identity-ed25519", crypto_1.default.EdDSA.seedLength);
|
|
75
|
+
const exchangeSeed = crypto_1.default.hkdf(seed, new Uint8Array(crypto_1.default.ECDH.secretKeyLength).fill(0), "identity-x25519", crypto_1.default.ECDH.secretKeyLength);
|
|
76
|
+
const signatureKeyPair = crypto_1.default.EdDSA.keyPairFromSeed(signatureSeed);
|
|
77
|
+
const exchangeKeyPair = crypto_1.default.ECDH.keyPair(exchangeSeed);
|
|
78
|
+
return types_1.PrivateIdentityKey.from(signatureKeyPair.secretKey, exchangeKeyPair.secretKey);
|
|
63
79
|
}
|
|
64
|
-
|
|
65
|
-
Object.defineProperty(exports, "UserId", { enumerable: true, get: function () { return types_1.UserId; } });
|
|
66
|
-
Object.defineProperty(exports, "IdentityKeys", { enumerable: true, get: function () { return types_1.IdentityKeys; } });
|
|
67
|
-
Object.defineProperty(exports, "Protocols", { enumerable: true, get: function () { return types_1.Protocols; } });
|
|
68
|
-
Object.defineProperty(exports, "Datagram", { enumerable: true, get: function () { return types_1.Datagram; } });
|
|
69
|
-
Object.defineProperty(exports, "EncryptedData", { enumerable: true, get: function () { return types_1.EncryptedData; } });
|
|
80
|
+
__exportStar(require("./types"), exports);
|
package/node.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Database, LocalStorage, Crypto, KeyExchangeDataBundle, KeyExchangeData } from "@freesignal/interfaces";
|
|
2
|
+
import { Datagram, IdentityKey, PrivateIdentityKey, Protocols, UserId } from "./types";
|
|
3
|
+
import { KeyExchange } from "./x3dh";
|
|
4
|
+
import { ExportedKeySession, KeySession } from "./double-ratchet";
|
|
5
|
+
export declare class FreeSignalNode {
|
|
6
|
+
protected readonly privateIdentityKey: PrivateIdentityKey;
|
|
7
|
+
protected readonly sessions: Map<string, KeySession>;
|
|
8
|
+
protected readonly sessionsData: LocalStorage<string, ExportedKeySession>;
|
|
9
|
+
protected readonly users: LocalStorage<string, IdentityKey>;
|
|
10
|
+
protected readonly keyExchange: KeyExchange;
|
|
11
|
+
constructor(storage: Database<{
|
|
12
|
+
sessions: LocalStorage<string, ExportedKeySession>;
|
|
13
|
+
keyExchange: LocalStorage<string, Crypto.KeyPair>;
|
|
14
|
+
users: LocalStorage<string, IdentityKey>;
|
|
15
|
+
}>, privateIdentityKey?: PrivateIdentityKey);
|
|
16
|
+
get userId(): UserId;
|
|
17
|
+
get identityKey(): IdentityKey;
|
|
18
|
+
generateKeyData(): Promise<KeyExchangeData>;
|
|
19
|
+
generateKeyBundle(length?: number): Promise<KeyExchangeDataBundle>;
|
|
20
|
+
encrypt(receiverId: string, protocol: Protocols, data: Uint8Array): Promise<Datagram>;
|
|
21
|
+
sendHandshake(data: KeyExchangeData): Promise<Datagram>;
|
|
22
|
+
sendData<T>(receiverId: string, data: T): Promise<Datagram>;
|
|
23
|
+
sendRelay(receiverId: string, data: Datagram): Promise<Datagram>;
|
|
24
|
+
sendDiscover(receiverId: string, discoverId: string): Promise<Datagram>;
|
|
25
|
+
decrypt(datagram: Datagram): Promise<Uint8Array>;
|
|
26
|
+
receive<T extends Uint8Array | UserId | Datagram | UserId | void>(datagram: Datagram | Uint8Array): Promise<T>;
|
|
27
|
+
}
|
package/node.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.FreeSignalNode = void 0;
|
|
13
|
+
const types_1 = require("./types");
|
|
14
|
+
const x3dh_1 = require("./x3dh");
|
|
15
|
+
const _1 = require(".");
|
|
16
|
+
const utils_1 = require("@freesignal/utils");
|
|
17
|
+
class FreeSignalNode {
|
|
18
|
+
constructor(storage, privateIdentityKey) {
|
|
19
|
+
this.sessions = new Map();
|
|
20
|
+
this.privateIdentityKey = privateIdentityKey !== null && privateIdentityKey !== void 0 ? privateIdentityKey : (0, _1.createIdentity)();
|
|
21
|
+
this.sessionsData = storage.sessions;
|
|
22
|
+
this.keyExchange = new x3dh_1.KeyExchange({ keys: storage.keyExchange, sessions: storage.sessions }, this.privateIdentityKey);
|
|
23
|
+
this.users = storage.users;
|
|
24
|
+
/*Array.from(this.sessionsData.entries()).forEach(([userId, sessionData]) => {
|
|
25
|
+
this.sessions.set(userId, KeySession.from(sessionData, this.sessionsData));
|
|
26
|
+
});*/
|
|
27
|
+
}
|
|
28
|
+
get userId() {
|
|
29
|
+
return types_1.UserId.fromKey(this.privateIdentityKey.identityKey);
|
|
30
|
+
}
|
|
31
|
+
get identityKey() {
|
|
32
|
+
return this.privateIdentityKey.identityKey;
|
|
33
|
+
}
|
|
34
|
+
generateKeyData() {
|
|
35
|
+
return this.keyExchange.generateData();
|
|
36
|
+
}
|
|
37
|
+
;
|
|
38
|
+
generateKeyBundle(length) {
|
|
39
|
+
return this.keyExchange.generateBundle(length);
|
|
40
|
+
}
|
|
41
|
+
;
|
|
42
|
+
encrypt(receiverId, protocol, data) {
|
|
43
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
44
|
+
const session = this.sessions.get(receiverId);
|
|
45
|
+
if (!session)
|
|
46
|
+
throw new Error("Session not found for user: " + receiverId);
|
|
47
|
+
return new types_1.Datagram(this.userId.toString(), receiverId, protocol, yield session.encrypt(data));
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
sendHandshake(data) {
|
|
51
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
52
|
+
const { session, message, identityKey } = yield this.keyExchange.digestData(data);
|
|
53
|
+
const remoteId = types_1.UserId.fromKey(identityKey);
|
|
54
|
+
this.sessions.set(remoteId.toString(), session);
|
|
55
|
+
return new types_1.Datagram(this.userId.toString(), types_1.UserId.fromKey(data.identityKey).toString(), types_1.Protocols.HANDSHAKE, (0, utils_1.encodeData)(message));
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
sendData(receiverId, data) {
|
|
59
|
+
return this.encrypt(receiverId, types_1.Protocols.MESSAGE, (0, utils_1.encodeData)(data));
|
|
60
|
+
}
|
|
61
|
+
sendRelay(receiverId, data) {
|
|
62
|
+
return this.encrypt(receiverId, types_1.Protocols.RELAY, (0, utils_1.encodeData)(data));
|
|
63
|
+
}
|
|
64
|
+
sendDiscover(receiverId, discoverId) {
|
|
65
|
+
return this.encrypt(receiverId, types_1.Protocols.DISCOVER, (0, utils_1.encodeData)(discoverId));
|
|
66
|
+
}
|
|
67
|
+
decrypt(datagram) {
|
|
68
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
69
|
+
const userId = datagram.sender;
|
|
70
|
+
const session = this.sessions.get(userId);
|
|
71
|
+
if (!session)
|
|
72
|
+
throw new Error("Session not found for user: " + userId);
|
|
73
|
+
if (!datagram.payload)
|
|
74
|
+
throw new Error("Missing payload");
|
|
75
|
+
const decrypted = yield session.decrypt(datagram.payload);
|
|
76
|
+
if (!decrypted)
|
|
77
|
+
throw new Error("Decryption failed");
|
|
78
|
+
return decrypted;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
receive(datagram) {
|
|
82
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
83
|
+
if (datagram instanceof Uint8Array)
|
|
84
|
+
datagram = types_1.Datagram.from(datagram);
|
|
85
|
+
switch (datagram.protocol) {
|
|
86
|
+
case types_1.Protocols.HANDSHAKE:
|
|
87
|
+
if (!datagram.payload)
|
|
88
|
+
throw new Error("Missing payload");
|
|
89
|
+
const data = (0, utils_1.decodeData)(datagram.payload);
|
|
90
|
+
const { session, identityKey } = yield this.keyExchange.digestMessage(data);
|
|
91
|
+
this.sessions.set(types_1.UserId.fromKey(identityKey).toString(), session);
|
|
92
|
+
return;
|
|
93
|
+
case types_1.Protocols.MESSAGE:
|
|
94
|
+
return yield this.decrypt(datagram);
|
|
95
|
+
case types_1.Protocols.RELAY:
|
|
96
|
+
return (0, utils_1.decodeData)(yield this.decrypt(datagram));
|
|
97
|
+
case types_1.Protocols.DISCOVER:
|
|
98
|
+
return types_1.UserId.from((0, utils_1.decodeData)(yield this.decrypt(datagram)));
|
|
99
|
+
default:
|
|
100
|
+
throw new Error("Invalid protocol");
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
exports.FreeSignalNode = FreeSignalNode;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@freesignal/protocol",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
4
4
|
"description": "Signal Protocol implementation in javascript",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"author": "Christian Braghette",
|
|
@@ -12,13 +12,8 @@
|
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@freesignal/crypto": "^0.3.0",
|
|
15
|
-
"@freesignal/interfaces": "^0.
|
|
16
|
-
"@freesignal/utils": "^1.
|
|
17
|
-
"base64-js": "^1.5.1",
|
|
18
|
-
"fflate": "^0.8.2",
|
|
19
|
-
"js-sha3": "^0.9.3",
|
|
20
|
-
"tweetnacl": "^1.0.3",
|
|
21
|
-
"uuid": "^11.1.0"
|
|
15
|
+
"@freesignal/interfaces": "^0.2.0",
|
|
16
|
+
"@freesignal/utils": "^1.3.0"
|
|
22
17
|
},
|
|
23
18
|
"devDependencies": {
|
|
24
19
|
"@types/node": "^24.2.1"
|