@freesignal/protocol 0.1.0 → 0.1.5
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/README.md +34 -0
- package/crypto.d.ts +15 -6
- package/crypto.js +24 -4
- package/data.d.ts +14 -54
- package/data.js +76 -132
- package/double-ratchet.d.ts +26 -42
- package/double-ratchet.js +90 -142
- package/index.d.ts +46 -0
- package/index.js +66 -0
- package/package.json +2 -1
- package/test.js +20 -6
- package/types.d.ts +61 -0
- package/types.js +29 -0
- package/utils.js +1 -0
- package/x3dh.d.ts +17 -47
- package/x3dh.js +86 -90
package/double-ratchet.js
CHANGED
|
@@ -21,37 +21,25 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
21
21
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
22
22
|
};
|
|
23
23
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
-
exports.
|
|
25
|
-
exports.createSession = createSession;
|
|
24
|
+
exports.EncryptedData = exports.KeySession = void 0;
|
|
26
25
|
const crypto_1 = __importDefault(require("./crypto"));
|
|
27
26
|
const utils_1 = require("./utils");
|
|
28
|
-
/**
|
|
29
|
-
* Creates a new Double Ratchet session.
|
|
30
|
-
*
|
|
31
|
-
* @param opts.remoteKey The public key of the remote party.
|
|
32
|
-
* @param opts.preSharedKey An optional pre-shared key to initialize the session.
|
|
33
|
-
*
|
|
34
|
-
* @returns A new Double Ratchet session.
|
|
35
|
-
*/
|
|
36
|
-
function createSession(opts) {
|
|
37
|
-
return new Session(opts);
|
|
38
|
-
}
|
|
39
27
|
/**
|
|
40
28
|
* Represents a secure Double Ratchet session.
|
|
41
29
|
* Used for forward-secure encryption and decryption of messages.
|
|
42
30
|
*/
|
|
43
|
-
class
|
|
31
|
+
class KeySession {
|
|
44
32
|
constructor(opts = {}) {
|
|
45
|
-
this.keyPair = crypto_1.default.ECDH.keyPair();
|
|
46
33
|
this.sendingCount = 0;
|
|
47
34
|
this.previousCount = 0;
|
|
48
35
|
this.receivingCount = 0;
|
|
49
36
|
this.previousKeys = new KeyMap();
|
|
50
|
-
|
|
51
|
-
|
|
37
|
+
this.keyPair = crypto_1.default.ECDH.keyPair(opts.secretKey);
|
|
38
|
+
if (opts.rootKey)
|
|
39
|
+
this.rootKey = opts.rootKey;
|
|
52
40
|
if (opts.remoteKey) {
|
|
53
41
|
this._remoteKey = opts.remoteKey;
|
|
54
|
-
this.sendingChain = this.
|
|
42
|
+
this.sendingChain = this.ratchetKeys();
|
|
55
43
|
}
|
|
56
44
|
}
|
|
57
45
|
/**
|
|
@@ -66,46 +54,43 @@ class Session {
|
|
|
66
54
|
* The last known remote public key.
|
|
67
55
|
*/
|
|
68
56
|
get remoteKey() { return this._remoteKey; }
|
|
69
|
-
/**
|
|
70
|
-
* Set remote public key.
|
|
71
|
-
*/
|
|
72
57
|
setRemoteKey(key) {
|
|
73
58
|
this._remoteKey = key;
|
|
74
|
-
this.receivingChain = this.
|
|
75
|
-
if (this.receivingCount > (
|
|
59
|
+
this.receivingChain = this.ratchetKeys();
|
|
60
|
+
if (this.receivingCount > (EncryptedDataConstructor.maxCount - KeySession.skipLimit * 2))
|
|
76
61
|
this.receivingCount = 0;
|
|
77
62
|
this.previousCount = this.sendingCount;
|
|
78
63
|
this.keyPair = crypto_1.default.ECDH.keyPair();
|
|
79
|
-
this.sendingChain = this.
|
|
80
|
-
if (this.sendingCount > (
|
|
64
|
+
this.sendingChain = this.ratchetKeys();
|
|
65
|
+
if (this.sendingCount > (EncryptedDataConstructor.maxCount - KeySession.skipLimit * 2))
|
|
81
66
|
this.sendingCount = 0;
|
|
82
67
|
return this;
|
|
83
68
|
}
|
|
84
|
-
|
|
69
|
+
ratchetKeys(info) {
|
|
85
70
|
if (!this._remoteKey)
|
|
86
71
|
throw new Error();
|
|
87
72
|
const sharedKey = crypto_1.default.scalarMult(this.keyPair.secretKey, this._remoteKey);
|
|
88
73
|
if (!this.rootKey)
|
|
89
74
|
this.rootKey = crypto_1.default.hash(sharedKey);
|
|
90
|
-
const hashkey = crypto_1.default.hkdf(sharedKey, this.rootKey, info,
|
|
91
|
-
this.rootKey = hashkey.slice(0,
|
|
92
|
-
return hashkey.slice(
|
|
75
|
+
const hashkey = crypto_1.default.hkdf(sharedKey, this.rootKey, info, KeySession.keyLength * 2);
|
|
76
|
+
this.rootKey = hashkey.slice(0, KeySession.keyLength);
|
|
77
|
+
return hashkey.slice(KeySession.keyLength);
|
|
93
78
|
}
|
|
94
79
|
getSendingKey() {
|
|
95
80
|
if (!this.sendingChain)
|
|
96
81
|
throw new Error;
|
|
97
|
-
const
|
|
98
|
-
this.sendingChain =
|
|
82
|
+
const { chainKey, sharedKey } = KeySession.symmetricRatchet(this.sendingChain);
|
|
83
|
+
this.sendingChain = chainKey;
|
|
99
84
|
this.sendingCount++;
|
|
100
|
-
return
|
|
85
|
+
return sharedKey;
|
|
101
86
|
}
|
|
102
87
|
getReceivingKey() {
|
|
103
88
|
if (!this.receivingChain)
|
|
104
89
|
throw new Error();
|
|
105
|
-
const
|
|
106
|
-
this.receivingChain =
|
|
90
|
+
const { chainKey, sharedKey } = KeySession.symmetricRatchet(this.receivingChain);
|
|
91
|
+
this.receivingChain = chainKey;
|
|
107
92
|
this.receivingCount++;
|
|
108
|
-
return
|
|
93
|
+
return sharedKey;
|
|
109
94
|
}
|
|
110
95
|
/**
|
|
111
96
|
* Encrypts a message payload using the current sending chain.
|
|
@@ -114,17 +99,12 @@ class Session {
|
|
|
114
99
|
* @returns An EncryptedPayload or undefined if encryption fails.
|
|
115
100
|
*/
|
|
116
101
|
encrypt(message) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
return new EncryptedPayloadConstructor(this.sendingCount, this.previousCount, this.keyPair.publicKey, nonce, ciphertext);
|
|
124
|
-
}
|
|
125
|
-
catch (error) {
|
|
126
|
-
return undefined;
|
|
127
|
-
}
|
|
102
|
+
const key = this.getSendingKey();
|
|
103
|
+
if (this.sendingCount >= EncryptedDataConstructor.maxCount || this.previousCount >= EncryptedDataConstructor.maxCount)
|
|
104
|
+
throw new Error();
|
|
105
|
+
const nonce = crypto_1.default.randomBytes(EncryptedDataConstructor.nonceLength);
|
|
106
|
+
const ciphertext = crypto_1.default.box.encrypt(message, nonce, key);
|
|
107
|
+
return new EncryptedDataConstructor(this.sendingCount, this.previousCount, this.keyPair.publicKey, nonce, ciphertext);
|
|
128
108
|
}
|
|
129
109
|
/**
|
|
130
110
|
* Decrypts an encrypted message.
|
|
@@ -134,47 +114,44 @@ class Session {
|
|
|
134
114
|
*/
|
|
135
115
|
decrypt(payload) {
|
|
136
116
|
var _a;
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
this.previousKeys.set(this.receivingCount, this.getReceivingKey());
|
|
151
|
-
}
|
|
152
|
-
key = this.getReceivingKey();
|
|
153
|
-
}
|
|
154
|
-
else {
|
|
155
|
-
key = this.previousKeys.get(count);
|
|
117
|
+
const encrypted = EncryptedData.from(payload);
|
|
118
|
+
const publicKey = encrypted.publicKey;
|
|
119
|
+
if (!(0, utils_1.verifyUint8Array)(publicKey, this._remoteKey)) {
|
|
120
|
+
while (this.receivingCount < encrypted.previous)
|
|
121
|
+
this.previousKeys.set(this.receivingCount, this.getReceivingKey());
|
|
122
|
+
this.setRemoteKey(publicKey);
|
|
123
|
+
}
|
|
124
|
+
let key;
|
|
125
|
+
const count = encrypted.count;
|
|
126
|
+
if (this.receivingCount < count) {
|
|
127
|
+
let i = 0;
|
|
128
|
+
while (this.receivingCount < count - 1 && i < KeySession.skipLimit) {
|
|
129
|
+
this.previousKeys.set(this.receivingCount, this.getReceivingKey());
|
|
156
130
|
}
|
|
157
|
-
|
|
158
|
-
return undefined;
|
|
159
|
-
return (_a = crypto_1.default.box.decrypt(encrypted.ciphertext, encrypted.nonce, key)) !== null && _a !== void 0 ? _a : undefined;
|
|
131
|
+
key = this.getReceivingKey();
|
|
160
132
|
}
|
|
161
|
-
|
|
162
|
-
|
|
133
|
+
else {
|
|
134
|
+
key = this.previousKeys.get(count);
|
|
163
135
|
}
|
|
136
|
+
if (!key)
|
|
137
|
+
return undefined;
|
|
138
|
+
return (_a = crypto_1.default.box.decrypt(encrypted.ciphertext, encrypted.nonce, key)) !== null && _a !== void 0 ? _a : undefined;
|
|
164
139
|
}
|
|
165
140
|
/**
|
|
166
141
|
* Export the state of the session;
|
|
167
142
|
*/
|
|
168
143
|
export() {
|
|
169
|
-
return
|
|
144
|
+
return {
|
|
145
|
+
secretKey: (0, utils_1.encodeBase64)((0, utils_1.concatUint8Array)(this.keyPair.secretKey)),
|
|
170
146
|
remoteKey: (0, utils_1.encodeBase64)(this._remoteKey),
|
|
171
147
|
rootKey: (0, utils_1.encodeBase64)(this.rootKey),
|
|
172
148
|
sendingChain: (0, utils_1.encodeBase64)(this.sendingChain),
|
|
173
149
|
receivingChain: (0, utils_1.encodeBase64)(this.receivingChain),
|
|
174
150
|
sendingCount: this.sendingCount,
|
|
175
151
|
receivingCount: this.receivingCount,
|
|
176
|
-
|
|
177
|
-
|
|
152
|
+
previousCount: this.previousCount,
|
|
153
|
+
previousKeys: Array.from(this.previousKeys.entries())
|
|
154
|
+
};
|
|
178
155
|
}
|
|
179
156
|
/**
|
|
180
157
|
* Import a state.
|
|
@@ -184,28 +161,34 @@ class Session {
|
|
|
184
161
|
*/
|
|
185
162
|
static import(json) {
|
|
186
163
|
const data = JSON.parse(json);
|
|
187
|
-
const session = new
|
|
164
|
+
const session = new KeySession({ secretKey: (0, utils_1.decodeBase64)(data.secretKey), rootKey: (0, utils_1.decodeBase64)(data.rootKey) });
|
|
165
|
+
session._remoteKey = (0, utils_1.decodeBase64)(data.remoteKey);
|
|
188
166
|
session.sendingChain = (0, utils_1.decodeBase64)(data.sendingChain);
|
|
189
167
|
session.receivingChain = (0, utils_1.decodeBase64)(data.receivingChain);
|
|
190
168
|
session.sendingCount = data.sendingCount;
|
|
191
169
|
session.receivingCount = data.receivingCount;
|
|
170
|
+
session.previousCount = data.previousCount;
|
|
171
|
+
session.previousKeys = new KeyMap(data.previousKeys);
|
|
192
172
|
return session;
|
|
193
173
|
}
|
|
194
174
|
static symmetricRatchet(chain, salt, info) {
|
|
195
|
-
const hash = crypto_1.default.hkdf(chain, salt !== null && salt !== void 0 ? salt : new Uint8Array(), info,
|
|
196
|
-
return
|
|
175
|
+
const hash = crypto_1.default.hkdf(chain, salt !== null && salt !== void 0 ? salt : new Uint8Array(), info, KeySession.keyLength * 2);
|
|
176
|
+
return {
|
|
177
|
+
chainKey: new Uint8Array(hash.buffer, 0, KeySession.keyLength),
|
|
178
|
+
sharedKey: new Uint8Array(hash.buffer, KeySession.keyLength)
|
|
179
|
+
};
|
|
197
180
|
}
|
|
198
181
|
}
|
|
199
|
-
exports.
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
182
|
+
exports.KeySession = KeySession;
|
|
183
|
+
KeySession.skipLimit = 1000;
|
|
184
|
+
KeySession.version = 1;
|
|
185
|
+
KeySession.rootKeyLength = crypto_1.default.box.keyLength;
|
|
203
186
|
/**
|
|
204
187
|
* The fixed key length (in bytes) used throughout the Double Ratchet session.
|
|
205
188
|
* Typically 32 bytes (256 bits) for symmetric keys.
|
|
206
189
|
*/
|
|
207
|
-
|
|
208
|
-
class
|
|
190
|
+
KeySession.keyLength = 32;
|
|
191
|
+
class EncryptedData {
|
|
209
192
|
/**
|
|
210
193
|
* Static factory method that constructs an `EncryptedPayload` from a raw Uint8Array.
|
|
211
194
|
*
|
|
@@ -213,24 +196,27 @@ class EncryptedPayload {
|
|
|
213
196
|
* @returns An instance of `EncryptedPayload`.
|
|
214
197
|
*/
|
|
215
198
|
static from(array) {
|
|
216
|
-
return new
|
|
199
|
+
return new EncryptedDataConstructor(array);
|
|
217
200
|
}
|
|
218
201
|
}
|
|
219
|
-
exports.
|
|
220
|
-
class
|
|
202
|
+
exports.EncryptedData = EncryptedData;
|
|
203
|
+
class EncryptedDataConstructor {
|
|
221
204
|
constructor(...arrays) {
|
|
222
|
-
var _a;
|
|
223
205
|
arrays = arrays.filter(value => value !== undefined);
|
|
224
|
-
if (arrays[0] instanceof
|
|
206
|
+
if (arrays[0] instanceof EncryptedDataConstructor) {
|
|
225
207
|
this.raw = arrays[0].raw;
|
|
226
208
|
return this;
|
|
227
209
|
}
|
|
228
210
|
if (typeof arrays[0] === 'number')
|
|
229
|
-
arrays[0] = (0, utils_1.numberToUint8Array)(arrays[0],
|
|
211
|
+
arrays[0] = (0, utils_1.numberToUint8Array)(arrays[0], EncryptedDataConstructor.countLength);
|
|
230
212
|
if (typeof arrays[1] === 'number')
|
|
231
|
-
arrays[1] = (0, utils_1.numberToUint8Array)(arrays[1],
|
|
232
|
-
if (arrays.length
|
|
233
|
-
arrays.unshift(
|
|
213
|
+
arrays[1] = (0, utils_1.numberToUint8Array)(arrays[1], EncryptedDataConstructor.countLength);
|
|
214
|
+
if (arrays.length === 6) {
|
|
215
|
+
arrays.unshift(typeof arrays[5] === 'number' ? (0, utils_1.numberToUint8Array)(arrays[5]) : arrays[5]);
|
|
216
|
+
arrays.pop();
|
|
217
|
+
}
|
|
218
|
+
else if (arrays.length > 1) {
|
|
219
|
+
arrays.unshift((0, utils_1.numberToUint8Array)(KeySession.version));
|
|
234
220
|
}
|
|
235
221
|
this.raw = (0, utils_1.concatUint8Array)(...arrays);
|
|
236
222
|
}
|
|
@@ -241,32 +227,6 @@ class EncryptedPayloadConstructor {
|
|
|
241
227
|
get publicKey() { return new Uint8Array(this.raw.buffer, ...Offsets.publicKey.get); }
|
|
242
228
|
get nonce() { return new Uint8Array(this.raw.buffer, ...Offsets.nonce.get); }
|
|
243
229
|
get ciphertext() { return new Uint8Array(this.raw.buffer, Offsets.ciphertext.start); }
|
|
244
|
-
/*public get signature() { return this.signed ? new Uint8Array(this.raw.buffer, this.raw.length - EncryptedPayloadConstructor.signatureLength) : undefined }
|
|
245
|
-
|
|
246
|
-
public setSignature(signature: Uint8Array): this {
|
|
247
|
-
this.raw = concatUint8Array(this.raw, signature);
|
|
248
|
-
this.signed = true;
|
|
249
|
-
return this;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
public encodeUnsigned(): Uint8Array {
|
|
253
|
-
return !this.signed ? this.raw : new Uint8Array(this.raw.buffer, 0, this.raw.length - EncryptedPayloadConstructor.signatureLength);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
public encode(fixedLength?: number): Uint8Array {
|
|
257
|
-
if (fixedLength) {
|
|
258
|
-
var padStart = Math.floor(Math.random() * fixedLength - 2 - this.raw.length);
|
|
259
|
-
var padEnd = Math.floor(padStart + 2 + this.raw.length);
|
|
260
|
-
} else {
|
|
261
|
-
padStart = Math.floor(Math.random() * (EncryptedPayloadConstructor.maxPadLength - EncryptedPayloadConstructor.minPadLength) + 1) + EncryptedPayloadConstructor.minPadLength;
|
|
262
|
-
padEnd = Math.floor(Math.random() * (EncryptedPayloadConstructor.maxPadLength - EncryptedPayloadConstructor.minPadLength) + 1) + EncryptedPayloadConstructor.minPadLength;
|
|
263
|
-
}
|
|
264
|
-
return concatUint8Array(
|
|
265
|
-
randomBytes(padStart - 1).map((value) => value !== 255 ? value : (value - 1)),
|
|
266
|
-
new Uint8Array(1).fill(255), this.raw, new Uint8Array(1).fill(255),
|
|
267
|
-
randomBytes(padEnd).map((value) => value !== 255 ? value : (value - 1)),
|
|
268
|
-
);
|
|
269
|
-
}*/
|
|
270
230
|
encode() {
|
|
271
231
|
return this.raw;
|
|
272
232
|
}
|
|
@@ -277,26 +237,22 @@ class EncryptedPayloadConstructor {
|
|
|
277
237
|
previous: this.previous,
|
|
278
238
|
publicKey: (0, utils_1.encodeBase64)(this.publicKey),
|
|
279
239
|
nonce: (0, utils_1.encodeBase64)(this.nonce),
|
|
280
|
-
ciphertext: (0, utils_1.
|
|
281
|
-
//signature: encodeBase64(this.signature)
|
|
240
|
+
ciphertext: (0, utils_1.encodeBase64)(this.ciphertext)
|
|
282
241
|
};
|
|
283
242
|
}
|
|
284
243
|
toString() {
|
|
285
|
-
return (0, utils_1.
|
|
244
|
+
return (0, utils_1.encodeBase64)(this.raw);
|
|
286
245
|
}
|
|
287
246
|
toJSON() {
|
|
288
247
|
return JSON.stringify(this.decode());
|
|
289
248
|
}
|
|
290
249
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
//public static readonly maxPadLength = 14;
|
|
298
|
-
EncryptedPayloadConstructor.maxCount = 65536; //32768;
|
|
299
|
-
EncryptedPayloadConstructor.countLength = 2;
|
|
250
|
+
EncryptedDataConstructor.secretKeyLength = crypto_1.default.ECDH.secretKeyLength;
|
|
251
|
+
EncryptedDataConstructor.publicKeyLength = crypto_1.default.ECDH.publicKeyLength;
|
|
252
|
+
EncryptedDataConstructor.keyLength = crypto_1.default.box.keyLength;
|
|
253
|
+
EncryptedDataConstructor.nonceLength = crypto_1.default.box.nonceLength;
|
|
254
|
+
EncryptedDataConstructor.maxCount = 65536; //32768;
|
|
255
|
+
EncryptedDataConstructor.countLength = 2;
|
|
300
256
|
class Offsets {
|
|
301
257
|
static set(start, length) {
|
|
302
258
|
class Offset {
|
|
@@ -315,20 +271,12 @@ class Offsets {
|
|
|
315
271
|
}
|
|
316
272
|
Offsets.checksum = Offsets.set(0, 0);
|
|
317
273
|
Offsets.version = Offsets.set(Offsets.checksum.end, 1);
|
|
318
|
-
Offsets.count = Offsets.set(Offsets.version.end,
|
|
319
|
-
Offsets.previous = Offsets.set(Offsets.count.end,
|
|
320
|
-
Offsets.publicKey = Offsets.set(Offsets.previous.end,
|
|
321
|
-
Offsets.nonce = Offsets.set(Offsets.publicKey.end,
|
|
274
|
+
Offsets.count = Offsets.set(Offsets.version.end, EncryptedDataConstructor.countLength);
|
|
275
|
+
Offsets.previous = Offsets.set(Offsets.count.end, EncryptedDataConstructor.countLength);
|
|
276
|
+
Offsets.publicKey = Offsets.set(Offsets.previous.end, EncryptedDataConstructor.publicKeyLength);
|
|
277
|
+
Offsets.nonce = Offsets.set(Offsets.publicKey.end, EncryptedDataConstructor.nonceLength);
|
|
322
278
|
Offsets.ciphertext = Offsets.set(Offsets.nonce.end, undefined);
|
|
323
|
-
class KeyMap {
|
|
324
|
-
constructor(iterable) {
|
|
325
|
-
return new KeyMapConstructor(iterable);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
class KeyMapConstructor extends Map {
|
|
329
|
-
constructor(iterable) {
|
|
330
|
-
super(iterable);
|
|
331
|
-
}
|
|
279
|
+
class KeyMap extends Map {
|
|
332
280
|
get(key) {
|
|
333
281
|
const out = super.get(key);
|
|
334
282
|
if (out && !super.delete(key))
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FreeSignal Protocol
|
|
3
|
+
*
|
|
4
|
+
* Copyright (C) 2025 Christian Braghette
|
|
5
|
+
*
|
|
6
|
+
* This program is free software: you can redistribute it and/or modify
|
|
7
|
+
* it under the terms of the GNU General Public License as published by
|
|
8
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
* (at your option) any later version.
|
|
10
|
+
*
|
|
11
|
+
* This program is distributed in the hope that it will be useful,
|
|
12
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
* GNU General Public License for more details.
|
|
15
|
+
*
|
|
16
|
+
* You should have received a copy of the GNU General Public License
|
|
17
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
18
|
+
*/
|
|
19
|
+
import crypto from "./crypto";
|
|
20
|
+
import { LocalStorage } from "./types";
|
|
21
|
+
import { KeySession } from "./double-ratchet";
|
|
22
|
+
import { KeyExchange } from "./x3dh";
|
|
23
|
+
/**
|
|
24
|
+
* Creates a new Double Ratchet session.
|
|
25
|
+
*
|
|
26
|
+
* @param opts.remoteKey The public key of the remote party.
|
|
27
|
+
* @param opts.preSharedKey An optional pre-shared key to initialize the session.
|
|
28
|
+
*
|
|
29
|
+
* @returns A new Double Ratchet session.
|
|
30
|
+
*/
|
|
31
|
+
export declare function createKeySession(opts?: {
|
|
32
|
+
secretKey?: Uint8Array;
|
|
33
|
+
remoteKey?: Uint8Array;
|
|
34
|
+
rootKey?: Uint8Array;
|
|
35
|
+
}): KeySession;
|
|
36
|
+
/**
|
|
37
|
+
* Creates a new X3DH session.
|
|
38
|
+
*
|
|
39
|
+
* @param signKeyPair
|
|
40
|
+
* @param bundleStore
|
|
41
|
+
* @returns A new X3DH session.
|
|
42
|
+
*/
|
|
43
|
+
export declare function createKeyExchange(signSecretKey: Uint8Array, boxSecretKey: Uint8Array, bundleStore?: LocalStorage<string, crypto.KeyPair>): KeyExchange;
|
|
44
|
+
export * from "./types";
|
|
45
|
+
export { Protocols, Datagram } from "./data";
|
|
46
|
+
export { EncryptedData } from "./double-ratchet";
|
package/index.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* FreeSignal Protocol
|
|
4
|
+
*
|
|
5
|
+
* Copyright (C) 2025 Christian Braghette
|
|
6
|
+
*
|
|
7
|
+
* This program is free software: you can redistribute it and/or modify
|
|
8
|
+
* it under the terms of the GNU General Public License as published by
|
|
9
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
10
|
+
* (at your option) any later version.
|
|
11
|
+
*
|
|
12
|
+
* This program is distributed in the hope that it will be useful,
|
|
13
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
* GNU General Public License for more details.
|
|
16
|
+
*
|
|
17
|
+
* You should have received a copy of the GNU General Public License
|
|
18
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
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
|
+
};
|
|
34
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
|
+
exports.EncryptedData = exports.Datagram = exports.Protocols = void 0;
|
|
36
|
+
exports.createKeySession = createKeySession;
|
|
37
|
+
exports.createKeyExchange = createKeyExchange;
|
|
38
|
+
const double_ratchet_1 = require("./double-ratchet");
|
|
39
|
+
const x3dh_1 = require("./x3dh");
|
|
40
|
+
/**
|
|
41
|
+
* Creates a new Double Ratchet session.
|
|
42
|
+
*
|
|
43
|
+
* @param opts.remoteKey The public key of the remote party.
|
|
44
|
+
* @param opts.preSharedKey An optional pre-shared key to initialize the session.
|
|
45
|
+
*
|
|
46
|
+
* @returns A new Double Ratchet session.
|
|
47
|
+
*/
|
|
48
|
+
function createKeySession(opts) {
|
|
49
|
+
return new double_ratchet_1.KeySession(opts);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Creates a new X3DH session.
|
|
53
|
+
*
|
|
54
|
+
* @param signKeyPair
|
|
55
|
+
* @param bundleStore
|
|
56
|
+
* @returns A new X3DH session.
|
|
57
|
+
*/
|
|
58
|
+
function createKeyExchange(signSecretKey, boxSecretKey, bundleStore) {
|
|
59
|
+
return new x3dh_1.KeyExchange(signSecretKey, boxSecretKey, bundleStore);
|
|
60
|
+
}
|
|
61
|
+
__exportStar(require("./types"), exports);
|
|
62
|
+
var data_1 = require("./data");
|
|
63
|
+
Object.defineProperty(exports, "Protocols", { enumerable: true, get: function () { return data_1.Protocols; } });
|
|
64
|
+
Object.defineProperty(exports, "Datagram", { enumerable: true, get: function () { return data_1.Datagram; } });
|
|
65
|
+
var double_ratchet_2 = require("./double-ratchet");
|
|
66
|
+
Object.defineProperty(exports, "EncryptedData", { enumerable: true, get: function () { return double_ratchet_2.EncryptedData; } });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@freesignal/protocol",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Signal Protocol implementation in javascript",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"author": "Christian Braghette",
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"base64-js": "^1.5.1",
|
|
15
|
+
"fflate": "^0.8.2",
|
|
15
16
|
"js-sha3": "^0.9.3",
|
|
16
17
|
"tweetnacl": "^1.0.3",
|
|
17
18
|
"uuid": "^11.1.0"
|
package/test.js
CHANGED
|
@@ -2,16 +2,30 @@
|
|
|
2
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
|
+
var _a, _b;
|
|
5
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const
|
|
7
|
+
const _1 = require(".");
|
|
7
8
|
const crypto_1 = __importDefault(require("./crypto"));
|
|
9
|
+
const data_1 = require("./data");
|
|
8
10
|
const utils_1 = require("./utils");
|
|
9
|
-
const bob =
|
|
10
|
-
const alice =
|
|
11
|
-
const bobmessage = bob.
|
|
12
|
-
const {
|
|
13
|
-
|
|
11
|
+
const bob = (0, _1.createKeyExchange)(crypto_1.default.EdDSA.keyPair().secretKey, crypto_1.default.ECDH.keyPair().secretKey);
|
|
12
|
+
const alice = (0, _1.createKeyExchange)(crypto_1.default.EdDSA.keyPair().secretKey, crypto_1.default.ECDH.keyPair().secretKey);
|
|
13
|
+
const bobmessage = bob.generateData();
|
|
14
|
+
const { session: alicesession, message: aliceack } = alice.digestData(bobmessage);
|
|
15
|
+
const { session: bobsession, cleartext } = (_a = bob.digestMessage(aliceack)) !== null && _a !== void 0 ? _a : {};
|
|
16
|
+
if (bobsession && cleartext) {
|
|
14
17
|
console.log("Session established successfully between Alice and Bob.");
|
|
18
|
+
const datagram = data_1.Datagram.create(bob.signatureKey, alice.signatureKey, data_1.Protocols.MESSAGE, (_b = bobsession.encrypt((0, utils_1.decodeUTF8)("Hi Alice!"))) === null || _b === void 0 ? void 0 : _b.encode());
|
|
19
|
+
//console.log(datagram.payload);
|
|
20
|
+
const msg = datagram.encode();
|
|
21
|
+
console.log((0, utils_1.encodeUTF8)(alicesession.decrypt(data_1.Datagram.from(msg).payload)));
|
|
22
|
+
if (alicesession.handshaked && bobsession.handshaked)
|
|
23
|
+
console.log("Successfully handshaked");
|
|
24
|
+
else
|
|
25
|
+
console.log("Error during handshake");
|
|
26
|
+
const longmsg = data_1.Datagram.create(alice.signatureKey, bob.signatureKey, data_1.Protocols.MESSAGE, alicesession.encrypt(new Uint8Array(1000000).fill(33).map(val => val + Math.floor(Math.random() * 93))));
|
|
27
|
+
console.log(longmsg.encode().length);
|
|
28
|
+
console.log(longmsg.encode(false).length);
|
|
15
29
|
}
|
|
16
30
|
else
|
|
17
31
|
console.log("Error");
|
package/types.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FreeSignal Protocol
|
|
3
|
+
*
|
|
4
|
+
* Copyright (C) 2025 Christian Braghette
|
|
5
|
+
*
|
|
6
|
+
* This program is free software: you can redistribute it and/or modify
|
|
7
|
+
* it under the terms of the GNU General Public License as published by
|
|
8
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
* (at your option) any later version.
|
|
10
|
+
*
|
|
11
|
+
* This program is distributed in the hope that it will be useful,
|
|
12
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
* GNU General Public License for more details.
|
|
15
|
+
*
|
|
16
|
+
* You should have received a copy of the GNU General Public License
|
|
17
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
18
|
+
*/
|
|
19
|
+
/** */
|
|
20
|
+
export interface Encodable {
|
|
21
|
+
encode(): Uint8Array;
|
|
22
|
+
toString(): string;
|
|
23
|
+
toJSON(): string;
|
|
24
|
+
}
|
|
25
|
+
export declare namespace Encodable {
|
|
26
|
+
function isEncodable(obj: any): boolean;
|
|
27
|
+
}
|
|
28
|
+
type LocalStorageIterator<T> = Iterable<T>;
|
|
29
|
+
export interface LocalStorage<K, T> {
|
|
30
|
+
set(key: K, value: T): this;
|
|
31
|
+
get(key: K): T | undefined;
|
|
32
|
+
has(key: K): boolean;
|
|
33
|
+
delete(key: K): boolean;
|
|
34
|
+
entries(): LocalStorageIterator<[K, T]>;
|
|
35
|
+
}
|
|
36
|
+
export interface KeyExchangeData {
|
|
37
|
+
readonly version: number;
|
|
38
|
+
readonly publicKey: string;
|
|
39
|
+
readonly identityKey: string;
|
|
40
|
+
readonly signedPreKey: string;
|
|
41
|
+
readonly signature: string;
|
|
42
|
+
readonly onetimePreKey: string;
|
|
43
|
+
}
|
|
44
|
+
export interface KeyExchangeSynMessage {
|
|
45
|
+
readonly version: number;
|
|
46
|
+
readonly publicKey: string;
|
|
47
|
+
readonly identityKey: string;
|
|
48
|
+
readonly ephemeralKey: string;
|
|
49
|
+
readonly signedPreKeyHash: string;
|
|
50
|
+
readonly onetimePreKeyHash: string;
|
|
51
|
+
readonly associatedData: string;
|
|
52
|
+
}
|
|
53
|
+
export interface KeyExchangeDataBundle {
|
|
54
|
+
readonly version: number;
|
|
55
|
+
readonly publicKey: string;
|
|
56
|
+
readonly identityKey: string;
|
|
57
|
+
readonly signedPreKey: string;
|
|
58
|
+
readonly signature: string;
|
|
59
|
+
readonly onetimePreKey: string[];
|
|
60
|
+
}
|
|
61
|
+
export {};
|
package/types.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* FreeSignal Protocol
|
|
4
|
+
*
|
|
5
|
+
* Copyright (C) 2025 Christian Braghette
|
|
6
|
+
*
|
|
7
|
+
* This program is free software: you can redistribute it and/or modify
|
|
8
|
+
* it under the terms of the GNU General Public License as published by
|
|
9
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
10
|
+
* (at your option) any later version.
|
|
11
|
+
*
|
|
12
|
+
* This program is distributed in the hope that it will be useful,
|
|
13
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
* GNU General Public License for more details.
|
|
16
|
+
*
|
|
17
|
+
* You should have received a copy of the GNU General Public License
|
|
18
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.Encodable = void 0;
|
|
22
|
+
var Encodable;
|
|
23
|
+
(function (Encodable) {
|
|
24
|
+
const properties = ['encode', 'toString', 'toJSON'];
|
|
25
|
+
function isEncodable(obj) {
|
|
26
|
+
return !properties.some(prop => !obj[prop]);
|
|
27
|
+
}
|
|
28
|
+
Encodable.isEncodable = isEncodable;
|
|
29
|
+
})(Encodable || (exports.Encodable = Encodable = {}));
|
package/utils.js
CHANGED
|
@@ -135,6 +135,7 @@ function verifyUint8Array(a, ...b) {
|
|
|
135
135
|
* @returns A Uint8Array
|
|
136
136
|
*/
|
|
137
137
|
function concatUint8Array(...arrays) {
|
|
138
|
+
//arrays = arrays.filter(array => array != undefined);
|
|
138
139
|
const out = new Uint8Array(arrays.map(value => value.length).reduce((prev, curr) => prev + curr));
|
|
139
140
|
let offset = 0;
|
|
140
141
|
arrays.forEach(array => {
|