@freesignal/protocol 0.1.0 → 0.1.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/data.d.ts CHANGED
@@ -104,4 +104,12 @@ declare class MessageConstructor implements Encodable, Message {
104
104
  toString(): string;
105
105
  toJSON(): string;
106
106
  }
107
+ type LocalStorageIterator<T> = Iterable<T>;
108
+ export interface LocalStorage<K, T> {
109
+ set(key: K, value: T): this;
110
+ get(key: K): T | undefined;
111
+ has(key: K): boolean;
112
+ delete(key: K): boolean;
113
+ entries(): LocalStorageIterator<[K, T]>;
114
+ }
107
115
  export {};
package/data.js CHANGED
@@ -84,7 +84,7 @@ class DatagramConstructor {
84
84
  constructor(data, receiver, protocol, payload) {
85
85
  if (!receiver && !protocol && !payload) {
86
86
  if (data instanceof Uint8Array) {
87
- const obj = (0, utils_1.encodeUTF8)(data).split(',');
87
+ const obj = (0, utils_1.encodeUTF8)(data).split(':');
88
88
  this.id = obj[0];
89
89
  this.version = parseInt(obj[1]);
90
90
  this.sender = obj[2];
@@ -125,7 +125,7 @@ class DatagramConstructor {
125
125
  this.receiver,
126
126
  Protocols.toCode(this.protocol).toString(),
127
127
  this.payload ? this.payload : undefined, this.createdAt
128
- ].filter(x => x !== undefined).join(','));
128
+ ].filter(x => x !== undefined).join(':'));
129
129
  }
130
130
  toString() {
131
131
  return this.toJSON();
@@ -174,7 +174,7 @@ class MessageConstructor {
174
174
  this.text = "";
175
175
  }
176
176
  else if (opts instanceof Uint8Array) {
177
- const arr = (0, utils_1.encodeUTF8)(opts).split(',');
177
+ const arr = (0, utils_1.encodeUTF8)(opts).split(':');
178
178
  this.version = arr[0];
179
179
  this.text = (_a = arr[1]) !== null && _a !== void 0 ? _a : "";
180
180
  this.group = arr[2];
@@ -215,7 +215,7 @@ class MessageConstructor {
215
215
  return this;
216
216
  }
217
217
  encode() {
218
- return (0, utils_1.decodeUTF8)([this.version, this.text, this.group, JSON.stringify(this.attachments || [])].join(','));
218
+ return (0, utils_1.decodeUTF8)([this.version, this.text, this.group, JSON.stringify(this.attachments || [])].join(':'));
219
219
  }
220
220
  toString() {
221
221
  return this.toJSON();
@@ -17,23 +17,22 @@
17
17
  * along with this program. If not, see <https://www.gnu.org/licenses/>
18
18
  */
19
19
  import { Encodable } from "./data";
20
- /**
21
- * Creates a new Double Ratchet session.
22
- *
23
- * @param opts.remoteKey The public key of the remote party.
24
- * @param opts.preSharedKey An optional pre-shared key to initialize the session.
25
- *
26
- * @returns A new Double Ratchet session.
27
- */
28
- export declare function createSession(opts?: {
29
- remoteKey?: Uint8Array;
30
- preSharedKey?: Uint8Array;
31
- }): Session;
20
+ type ExportedKeySession = {
21
+ secretKey: string;
22
+ remoteKey: string;
23
+ rootKey: string;
24
+ sendingChain: string;
25
+ receivingChain: string;
26
+ sendingCount: number;
27
+ receivingCount: number;
28
+ previousCount: number;
29
+ previousKeys: [number, Uint8Array][];
30
+ };
32
31
  /**
33
32
  * Represents a secure Double Ratchet session.
34
33
  * Used for forward-secure encryption and decryption of messages.
35
34
  */
36
- export declare class Session {
35
+ export declare class KeySession {
37
36
  private static readonly skipLimit;
38
37
  static readonly version = 1;
39
38
  static readonly rootKeyLength: number;
@@ -47,8 +46,9 @@ export declare class Session {
47
46
  private receivingCount;
48
47
  private previousKeys;
49
48
  constructor(opts?: {
49
+ secretKey?: Uint8Array;
50
50
  remoteKey?: Uint8Array;
51
- preSharedKey?: Uint8Array;
51
+ rootKey?: Uint8Array;
52
52
  });
53
53
  /**
54
54
  * Whether both the sending and receiving chains are initialized.
@@ -62,11 +62,8 @@ export declare class Session {
62
62
  * The last known remote public key.
63
63
  */
64
64
  get remoteKey(): Uint8Array | undefined;
65
- /**
66
- * Set remote public key.
67
- */
68
- setRemoteKey(key: Uint8Array): this;
69
- private dhRatchet;
65
+ private setRemoteKey;
66
+ private ratchetKeys;
70
67
  private getSendingKey;
71
68
  private getReceivingKey;
72
69
  /**
@@ -75,7 +72,7 @@ export declare class Session {
75
72
  * @param message - The message as a Uint8Array.
76
73
  * @returns An EncryptedPayload or undefined if encryption fails.
77
74
  */
78
- encrypt(message: Uint8Array): EncryptedPayload | undefined;
75
+ encrypt(message: Uint8Array): EncryptedPayload;
79
76
  /**
80
77
  * Decrypts an encrypted message.
81
78
  *
@@ -86,14 +83,14 @@ export declare class Session {
86
83
  /**
87
84
  * Export the state of the session;
88
85
  */
89
- export(): string;
86
+ export(): ExportedKeySession;
90
87
  /**
91
88
  * Import a state.
92
89
  *
93
90
  * @param json string returned by `export()` method.
94
91
  * @returns session with the state parsed.
95
92
  */
96
- static import(json: string): Session;
93
+ static import(json: string): KeySession;
97
94
  /**
98
95
  * The fixed key length (in bytes) used throughout the Double Ratchet session.
99
96
  * Typically 32 bytes (256 bits) for symmetric keys.
@@ -134,26 +131,12 @@ export interface EncryptedPayload extends Encodable {
134
131
  * The encrypted message content.
135
132
  */
136
133
  readonly ciphertext: Uint8Array;
137
- /**
138
- * The payload signature.
139
- */
140
- /**
141
- * Set the signature of the payload.
142
- *
143
- * @param signature signature
144
- */
145
- /**
146
- * Return the payload without the signature.
147
- */
148
134
  /**
149
135
  * Serializes the payload into a Uint8Array for transport.
150
136
  */
151
137
  encode(): Uint8Array;
152
138
  /**
153
- * Decodes the payload into a readable object format.
154
- */
155
- /**
156
- * Returns the payload as a UTF-8 string.
139
+ * Returns the payload as a Base64 string.
157
140
  */
158
141
  toString(): string;
159
142
  /**
@@ -170,3 +153,4 @@ export declare class EncryptedPayload {
170
153
  */
171
154
  static from(array: Uint8Array | EncryptedPayload): EncryptedPayload;
172
155
  }
156
+ export {};
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.EncryptedPayload = exports.Session = void 0;
25
- exports.createSession = createSession;
24
+ exports.EncryptedPayload = 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 Session {
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
- if (opts.preSharedKey)
51
- this.rootKey = opts.preSharedKey;
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.dhRatchet();
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.dhRatchet();
75
- if (this.receivingCount > (EncryptedPayloadConstructor.maxCount - Session.skipLimit * 2))
59
+ this.receivingChain = this.ratchetKeys();
60
+ if (this.receivingCount > (EncryptedPayloadConstructor.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.dhRatchet();
80
- if (this.sendingCount > (EncryptedPayloadConstructor.maxCount - Session.skipLimit * 2))
64
+ this.sendingChain = this.ratchetKeys();
65
+ if (this.sendingCount > (EncryptedPayloadConstructor.maxCount - KeySession.skipLimit * 2))
81
66
  this.sendingCount = 0;
82
67
  return this;
83
68
  }
84
- dhRatchet(info) {
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, Session.keyLength * 2);
91
- this.rootKey = hashkey.slice(0, Session.keyLength);
92
- return hashkey.slice(Session.keyLength);
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 out = Session.symmetricRatchet(this.sendingChain);
98
- this.sendingChain = out[0];
82
+ const { chainKey, sharedKey } = KeySession.symmetricRatchet(this.sendingChain);
83
+ this.sendingChain = chainKey;
99
84
  this.sendingCount++;
100
- return out[1];
85
+ return sharedKey;
101
86
  }
102
87
  getReceivingKey() {
103
88
  if (!this.receivingChain)
104
89
  throw new Error();
105
- const out = Session.symmetricRatchet(this.receivingChain);
106
- this.receivingChain = out[0];
90
+ const { chainKey, sharedKey } = KeySession.symmetricRatchet(this.receivingChain);
91
+ this.receivingChain = chainKey;
107
92
  this.receivingCount++;
108
- return out[1];
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
- try {
118
- const key = this.getSendingKey();
119
- if (this.sendingCount >= EncryptedPayloadConstructor.maxCount || this.previousCount >= EncryptedPayloadConstructor.maxCount)
120
- throw new Error();
121
- const nonce = crypto_1.default.randomBytes(EncryptedPayloadConstructor.nonceLength);
122
- const ciphertext = crypto_1.default.box.encrypt(message, nonce, key);
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 >= EncryptedPayloadConstructor.maxCount || this.previousCount >= EncryptedPayloadConstructor.maxCount)
104
+ throw new Error();
105
+ const nonce = crypto_1.default.randomBytes(EncryptedPayloadConstructor.nonceLength);
106
+ const ciphertext = crypto_1.default.box.encrypt(message, nonce, key);
107
+ return new EncryptedPayloadConstructor(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
- try {
138
- const encrypted = EncryptedPayload.from(payload);
139
- const publicKey = encrypted.publicKey;
140
- if (!(0, utils_1.verifyUint8Array)(publicKey, this._remoteKey)) {
141
- while (this.receivingCount < encrypted.previous)
142
- this.previousKeys.set(this.receivingCount, this.getReceivingKey());
143
- this.setRemoteKey(publicKey);
144
- }
145
- let key;
146
- const count = encrypted.count;
147
- if (this.receivingCount < count) {
148
- let i = 0;
149
- while (this.receivingCount < count - 1 && i < Session.skipLimit) {
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 = EncryptedPayload.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
- if (!key)
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
- catch (error) {
162
- return undefined;
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 JSON.stringify({
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
- //previousCount: this.previousCount
177
- });
152
+ previousCount: this.previousCount,
153
+ previousKeys: Array.from(this.previousKeys.entries())
154
+ };
178
155
  }
179
156
  /**
180
157
  * Import a state.
@@ -184,27 +161,33 @@ class Session {
184
161
  */
185
162
  static import(json) {
186
163
  const data = JSON.parse(json);
187
- const session = new Session({ remoteKey: (0, utils_1.decodeBase64)(data.remoteKey), preSharedKey: (0, utils_1.decodeBase64)(data.rootKey) });
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, Session.keyLength * 2);
196
- return [new Uint8Array(hash.buffer, 0, Session.keyLength), new Uint8Array(hash.buffer, Session.keyLength)];
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.Session = Session;
200
- Session.skipLimit = 1000;
201
- Session.version = 1;
202
- Session.rootKeyLength = crypto_1.default.box.keyLength;
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
- Session.keyLength = 32;
190
+ KeySession.keyLength = 32;
208
191
  class EncryptedPayload {
209
192
  /**
210
193
  * Static factory method that constructs an `EncryptedPayload` from a raw Uint8Array.
@@ -230,7 +213,7 @@ class EncryptedPayloadConstructor {
230
213
  if (typeof arrays[1] === 'number')
231
214
  arrays[1] = (0, utils_1.numberToUint8Array)(arrays[1], EncryptedPayloadConstructor.countLength);
232
215
  if (arrays.length > 1) {
233
- arrays.unshift((_a = (typeof arrays[5] === 'number' ? (0, utils_1.numberToUint8Array)(arrays[5]) : arrays[5])) !== null && _a !== void 0 ? _a : (0, utils_1.numberToUint8Array)(Session.version));
216
+ arrays.unshift((_a = (typeof arrays[5] === 'number' ? (0, utils_1.numberToUint8Array)(arrays[5]) : arrays[5])) !== null && _a !== void 0 ? _a : (0, utils_1.numberToUint8Array)(KeySession.version));
234
217
  }
235
218
  this.raw = (0, utils_1.concatUint8Array)(...arrays);
236
219
  }
@@ -241,32 +224,6 @@ class EncryptedPayloadConstructor {
241
224
  get publicKey() { return new Uint8Array(this.raw.buffer, ...Offsets.publicKey.get); }
242
225
  get nonce() { return new Uint8Array(this.raw.buffer, ...Offsets.nonce.get); }
243
226
  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
227
  encode() {
271
228
  return this.raw;
272
229
  }
@@ -277,24 +234,20 @@ class EncryptedPayloadConstructor {
277
234
  previous: this.previous,
278
235
  publicKey: (0, utils_1.encodeBase64)(this.publicKey),
279
236
  nonce: (0, utils_1.encodeBase64)(this.nonce),
280
- ciphertext: (0, utils_1.encodeUTF8)(this.ciphertext),
281
- //signature: encodeBase64(this.signature)
237
+ ciphertext: (0, utils_1.encodeBase64)(this.ciphertext)
282
238
  };
283
239
  }
284
240
  toString() {
285
- return (0, utils_1.encodeUTF8)(this.raw);
241
+ return (0, utils_1.encodeBase64)(this.raw);
286
242
  }
287
243
  toJSON() {
288
244
  return JSON.stringify(this.decode());
289
245
  }
290
246
  }
291
- //public static readonly signatureLength = crypto.EdDSA.signatureLength;
292
247
  EncryptedPayloadConstructor.secretKeyLength = crypto_1.default.ECDH.secretKeyLength;
293
248
  EncryptedPayloadConstructor.publicKeyLength = crypto_1.default.ECDH.publicKeyLength;
294
249
  EncryptedPayloadConstructor.keyLength = crypto_1.default.box.keyLength;
295
250
  EncryptedPayloadConstructor.nonceLength = crypto_1.default.box.nonceLength;
296
- //public static readonly minPadLength = 6;
297
- //public static readonly maxPadLength = 14;
298
251
  EncryptedPayloadConstructor.maxCount = 65536; //32768;
299
252
  EncryptedPayloadConstructor.countLength = 2;
300
253
  class Offsets {
@@ -320,15 +273,7 @@ Offsets.previous = Offsets.set(Offsets.count.end, EncryptedPayloadConstructor.co
320
273
  Offsets.publicKey = Offsets.set(Offsets.previous.end, EncryptedPayloadConstructor.publicKeyLength);
321
274
  Offsets.nonce = Offsets.set(Offsets.publicKey.end, EncryptedPayloadConstructor.nonceLength);
322
275
  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
- }
276
+ class KeyMap extends Map {
332
277
  get(key) {
333
278
  const out = super.get(key);
334
279
  if (out && !super.delete(key))
package/index.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ import crypto from "./crypto";
2
+ import { LocalStorage } from "./data";
3
+ import { KeySession } from "./double-ratchet";
4
+ import { KeyExchange } from "./x3dh";
5
+ /**
6
+ * Creates a new Double Ratchet session.
7
+ *
8
+ * @param opts.remoteKey The public key of the remote party.
9
+ * @param opts.preSharedKey An optional pre-shared key to initialize the session.
10
+ *
11
+ * @returns A new Double Ratchet session.
12
+ */
13
+ export declare function createKeySession(opts?: {
14
+ secretKey?: Uint8Array;
15
+ remoteKey?: Uint8Array;
16
+ rootKey?: Uint8Array;
17
+ }): KeySession;
18
+ /**
19
+ * Creates a new X3DH session.
20
+ *
21
+ * @param signKeyPair
22
+ * @param bundleStore
23
+ * @returns A new X3DH session.
24
+ */
25
+ export declare function createKeyExchange(signKeyPair: crypto.KeyPair, bundleStore?: LocalStorage<string, crypto.KeyPair>): KeyExchange;
package/index.js ADDED
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createKeySession = createKeySession;
4
+ exports.createKeyExchange = createKeyExchange;
5
+ const double_ratchet_1 = require("./double-ratchet");
6
+ const x3dh_1 = require("./x3dh");
7
+ /**
8
+ * Creates a new Double Ratchet session.
9
+ *
10
+ * @param opts.remoteKey The public key of the remote party.
11
+ * @param opts.preSharedKey An optional pre-shared key to initialize the session.
12
+ *
13
+ * @returns A new Double Ratchet session.
14
+ */
15
+ function createKeySession(opts) {
16
+ return new double_ratchet_1.KeySession(opts);
17
+ }
18
+ /**
19
+ * Creates a new X3DH session.
20
+ *
21
+ * @param signKeyPair
22
+ * @param bundleStore
23
+ * @returns A new X3DH session.
24
+ */
25
+ function createKeyExchange(signKeyPair, bundleStore) {
26
+ return new x3dh_1.KeyExchange(signKeyPair, bundleStore);
27
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@freesignal/protocol",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Signal Protocol implementation in javascript",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "author": "Christian Braghette",
package/test.js CHANGED
@@ -2,16 +2,24 @@
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 x3dh_1 = require("./x3dh");
7
+ const _1 = require(".");
7
8
  const crypto_1 = __importDefault(require("./crypto"));
8
9
  const utils_1 = require("./utils");
9
- const bob = new x3dh_1.X3DH(crypto_1.default.EdDSA.keyPair());
10
- const alice = new x3dh_1.X3DH(crypto_1.default.EdDSA.keyPair());
10
+ const bob = (0, _1.createKeyExchange)(crypto_1.default.EdDSA.keyPair());
11
+ const alice = (0, _1.createKeyExchange)(crypto_1.default.EdDSA.keyPair());
11
12
  const bobmessage = bob.generateSyn();
12
- const { rootKey, ackMessage: aliceack } = alice.digestSyn(bobmessage);
13
- if ((0, utils_1.verifyUint8Array)(rootKey, bob.digestAck(aliceack))) {
13
+ const { session: alicesession, ackMessage: aliceack } = alice.digestSyn(bobmessage);
14
+ const { session: bobsession, cleartext } = (_a = bob.digestAck(aliceack)) !== null && _a !== void 0 ? _a : {};
15
+ if (bobsession && cleartext) {
14
16
  console.log("Session established successfully between Alice and Bob.");
17
+ const msg = (_b = bobsession.encrypt((0, utils_1.decodeUTF8)("Hi Alice!"))) === null || _b === void 0 ? void 0 : _b.encode();
18
+ console.log((0, utils_1.encodeUTF8)(alicesession.decrypt(msg)));
19
+ if (alicesession.handshaked && bobsession.handshaked)
20
+ console.log("Successfully handshaked");
21
+ else
22
+ console.log("Error during handshake");
15
23
  }
16
24
  else
17
25
  console.log("Error");
package/x3dh.d.ts CHANGED
@@ -17,58 +17,52 @@
17
17
  * along with this program. If not, see <https://www.gnu.org/licenses/>
18
18
  */
19
19
  import crypto from "./crypto";
20
- type BoxKeyPair = crypto.KeyPair;
21
- type SignKeyPair = crypto.KeyPair;
22
- type ExportedX3DH = [
23
- SignKeyPair,
24
- [
25
- Array<[string, BoxKeyPair]>,
26
- Array<[string, BoxKeyPair]>
27
- ]
28
- ];
20
+ import { LocalStorage } from "./data";
21
+ import { KeySession } from "./double-ratchet";
29
22
  interface SynMessage {
30
23
  readonly version: number;
31
- readonly PK: string;
32
- readonly IK: string;
33
- readonly SPK: string;
34
- readonly SPKsign: string;
35
- readonly OPK: string;
24
+ readonly publicKey: string;
25
+ readonly identityKey: string;
26
+ readonly signedPreKey: string;
27
+ readonly signature: string;
28
+ readonly onetimePreKey: string;
36
29
  }
37
30
  interface AckMessage {
38
31
  readonly version: number;
39
- readonly PK: string;
40
- readonly IK: string;
41
- readonly EK: string;
42
- readonly SPKhash: string;
43
- readonly OPKhash: string;
44
- readonly AD: string;
32
+ readonly publicKey: string;
33
+ readonly identityKey: string;
34
+ readonly ephemeralKey: string;
35
+ readonly signedPreKeyHash: string;
36
+ readonly onetimePreKeyHash: string;
37
+ readonly associatedData: string;
45
38
  }
46
39
  export interface Bundle {
47
40
  readonly version: number;
48
- readonly PK: string;
49
- readonly IK: string;
50
- readonly SPK: string;
51
- readonly SPKsign: string;
52
- readonly OPK: string[];
41
+ readonly publicKey: string;
42
+ readonly identityKey: string;
43
+ readonly signedPreKey: string;
44
+ readonly signature: string;
45
+ readonly onetimePreKeyHash: string[];
53
46
  }
54
- export declare class X3DH {
47
+ export declare class KeyExchange {
55
48
  static readonly version = 1;
56
49
  private static readonly hkdfInfo;
57
50
  private static readonly maxOPK;
58
- private readonly PK;
59
- private readonly IK;
51
+ private readonly publicKey;
52
+ private readonly identityKey;
60
53
  private readonly bundleStore;
61
- constructor(signKeyPair: SignKeyPair, instance?: [Iterable<[string, BoxKeyPair]>, Iterable<[string, BoxKeyPair]>]);
54
+ constructor(signKeyPair: crypto.KeyPair, bundleStore?: LocalStorage<string, crypto.KeyPair>);
62
55
  private generateSPK;
63
56
  private generateOPK;
64
57
  generateBundle(length?: number): Bundle;
65
58
  generateSyn(): SynMessage;
66
- digestSyn(message: SynMessage, encrypter?: (msg: Uint8Array, key: Uint8Array) => Uint8Array): {
67
- rootKey: Uint8Array;
59
+ digestSyn(message: SynMessage): {
60
+ session: KeySession;
68
61
  ackMessage: AckMessage;
69
62
  };
70
- digestAck(message: AckMessage, verifier?: (ciphertext: Uint8Array, key: Uint8Array) => boolean): Uint8Array | undefined;
71
- export(): ExportedX3DH;
72
- static import(input: ExportedX3DH): X3DH;
63
+ digestAck(message: AckMessage): {
64
+ session: KeySession;
65
+ cleartext: Uint8Array;
66
+ };
73
67
  }
74
68
  export {};
package/x3dh.js CHANGED
@@ -21,116 +21,106 @@ 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.X3DH = void 0;
24
+ exports.KeyExchange = void 0;
25
25
  const crypto_1 = __importDefault(require("./crypto"));
26
26
  const double_ratchet_1 = require("./double-ratchet");
27
27
  const utils_1 = require("./utils");
28
- class X3DH {
29
- constructor(signKeyPair, instance) {
30
- this.PK = signKeyPair;
31
- this.IK = crypto_1.default.ECDH.keyPair(crypto_1.default.hash(signKeyPair.secretKey));
32
- this.bundleStore = {
33
- SPK: new Map(instance ? instance[0] : []),
34
- OPK: new Map(instance ? instance[1] : [])
35
- };
28
+ class KeyExchange {
29
+ constructor(signKeyPair, bundleStore) {
30
+ this.publicKey = signKeyPair;
31
+ this.identityKey = crypto_1.default.ECDH.keyPair(crypto_1.default.hash(signKeyPair.secretKey));
32
+ this.bundleStore = bundleStore !== null && bundleStore !== void 0 ? bundleStore : new Map();
36
33
  }
37
34
  generateSPK() {
38
- const SPK = crypto_1.default.ECDH.keyPair();
39
- const SPKhash = crypto_1.default.hash(SPK.publicKey);
40
- this.bundleStore.SPK.set((0, utils_1.encodeBase64)(SPKhash), SPK);
41
- return { SPK, SPKhash };
35
+ const signedPreKey = crypto_1.default.ECDH.keyPair();
36
+ const signedPreKeyHash = crypto_1.default.hash(signedPreKey.publicKey);
37
+ this.bundleStore.set((0, utils_1.encodeBase64)(signedPreKeyHash), signedPreKey);
38
+ return { signedPreKey, signedPreKeyHash };
42
39
  }
43
40
  generateOPK(spkHash) {
44
- const OPK = crypto_1.default.ECDH.keyPair();
45
- const OPKhash = crypto_1.default.hash(OPK.publicKey);
46
- this.bundleStore.OPK.set((0, utils_1.encodeBase64)(spkHash).concat((0, utils_1.encodeBase64)(OPKhash)), OPK);
47
- return { OPK, OPKhash };
41
+ const onetimePreKey = crypto_1.default.ECDH.keyPair();
42
+ const onetimePreKeyHash = crypto_1.default.hash(onetimePreKey.publicKey);
43
+ this.bundleStore.set((0, utils_1.encodeBase64)(spkHash).concat((0, utils_1.encodeBase64)(onetimePreKeyHash)), onetimePreKey);
44
+ return { onetimePreKey, onetimePreKeyHash };
48
45
  }
49
46
  generateBundle(length) {
50
- const { SPK, SPKhash } = this.generateSPK();
51
- const OPK = new Array(length !== null && length !== void 0 ? length : X3DH.maxOPK).fill(0).map(() => this.generateOPK(SPKhash).OPK);
47
+ const { signedPreKey, signedPreKeyHash } = this.generateSPK();
48
+ const onetimePreKey = new Array(length !== null && length !== void 0 ? length : KeyExchange.maxOPK).fill(0).map(() => this.generateOPK(signedPreKeyHash).onetimePreKey);
52
49
  return {
53
- version: X3DH.version,
54
- PK: (0, utils_1.encodeBase64)(this.PK.publicKey),
55
- IK: (0, utils_1.encodeBase64)(this.IK.publicKey),
56
- SPK: (0, utils_1.encodeBase64)(SPK.publicKey),
57
- SPKsign: (0, utils_1.encodeBase64)(crypto_1.default.EdDSA.sign((0, utils_1.concatUint8Array)(crypto_1.default.hash(this.IK.publicKey), SPKhash), this.PK.secretKey)),
58
- OPK: OPK.map(opk => (0, utils_1.encodeBase64)(opk.publicKey))
50
+ version: KeyExchange.version,
51
+ publicKey: (0, utils_1.encodeBase64)(this.publicKey.publicKey),
52
+ identityKey: (0, utils_1.encodeBase64)(this.identityKey.publicKey),
53
+ signedPreKey: (0, utils_1.encodeBase64)(signedPreKey.publicKey),
54
+ signature: (0, utils_1.encodeBase64)(crypto_1.default.EdDSA.sign(signedPreKeyHash, this.publicKey.secretKey)),
55
+ onetimePreKeyHash: onetimePreKey.map(opk => (0, utils_1.encodeBase64)(opk.publicKey))
59
56
  };
60
57
  }
61
58
  generateSyn() {
62
- const { SPK, SPKhash } = this.generateSPK();
63
- const { OPK } = this.generateOPK(SPKhash);
59
+ const { signedPreKey, signedPreKeyHash } = this.generateSPK();
60
+ const { onetimePreKey } = this.generateOPK(signedPreKeyHash);
64
61
  return {
65
- version: X3DH.version,
66
- PK: (0, utils_1.encodeBase64)(this.PK.publicKey),
67
- IK: (0, utils_1.encodeBase64)(this.IK.publicKey),
68
- SPK: (0, utils_1.encodeBase64)(SPK.publicKey),
69
- SPKsign: (0, utils_1.encodeBase64)(crypto_1.default.EdDSA.sign((0, utils_1.concatUint8Array)(crypto_1.default.hash(this.IK.publicKey), SPKhash), this.PK.secretKey)),
70
- OPK: (0, utils_1.encodeBase64)(OPK.publicKey)
62
+ version: KeyExchange.version,
63
+ publicKey: (0, utils_1.encodeBase64)(this.publicKey.publicKey),
64
+ identityKey: (0, utils_1.encodeBase64)(this.identityKey.publicKey),
65
+ signedPreKey: (0, utils_1.encodeBase64)(signedPreKey.publicKey),
66
+ signature: (0, utils_1.encodeBase64)(crypto_1.default.EdDSA.sign(signedPreKeyHash, this.publicKey.secretKey)),
67
+ onetimePreKey: (0, utils_1.encodeBase64)(onetimePreKey.publicKey)
71
68
  };
72
69
  }
73
- digestSyn(message, encrypter) {
74
- const EK = crypto_1.default.ECDH.keyPair();
75
- const SPK = (0, utils_1.decodeBase64)(message.SPK);
76
- const IK = (0, utils_1.decodeBase64)(message.IK);
77
- const OPK = message.OPK ? (0, utils_1.decodeBase64)(message.OPK) : undefined;
78
- const spkHash = crypto_1.default.hash(SPK);
79
- const opkHash = OPK ? crypto_1.default.hash(OPK) : new Uint8Array();
70
+ digestSyn(message) {
71
+ const ephemeralKey = crypto_1.default.ECDH.keyPair();
72
+ const signedPreKey = (0, utils_1.decodeBase64)(message.signedPreKey);
73
+ const identityKey = (0, utils_1.decodeBase64)(message.identityKey);
74
+ const onetimePreKey = message.onetimePreKey ? (0, utils_1.decodeBase64)(message.onetimePreKey) : undefined;
75
+ const signedPreKeyHash = crypto_1.default.hash(signedPreKey);
76
+ const onetimePreKeyHash = onetimePreKey ? crypto_1.default.hash(onetimePreKey) : new Uint8Array();
80
77
  const rootKey = crypto_1.default.hkdf(new Uint8Array([
81
- ...crypto_1.default.scalarMult(this.IK.secretKey, SPK),
82
- ...crypto_1.default.scalarMult(EK.secretKey, IK),
83
- ...crypto_1.default.scalarMult(EK.secretKey, SPK),
84
- ...OPK ? crypto_1.default.scalarMult(EK.secretKey, OPK) : new Uint8Array()
85
- ]), new Uint8Array(double_ratchet_1.Session.rootKeyLength).fill(0), X3DH.hkdfInfo, double_ratchet_1.Session.rootKeyLength);
86
- if (!encrypter)
87
- encrypter = (msg, key) => crypto_1.default.box.encrypt(msg, new Uint8Array(crypto_1.default.box.nonceLength).fill(0), key);
78
+ ...crypto_1.default.scalarMult(this.identityKey.secretKey, signedPreKey),
79
+ ...crypto_1.default.scalarMult(ephemeralKey.secretKey, identityKey),
80
+ ...crypto_1.default.scalarMult(ephemeralKey.secretKey, signedPreKey),
81
+ ...onetimePreKey ? crypto_1.default.scalarMult(ephemeralKey.secretKey, onetimePreKey) : new Uint8Array()
82
+ ]), new Uint8Array(double_ratchet_1.KeySession.rootKeyLength).fill(0), KeyExchange.hkdfInfo, double_ratchet_1.KeySession.rootKeyLength);
83
+ const session = new double_ratchet_1.KeySession({ secretKey: this.identityKey.secretKey, remoteKey: identityKey, rootKey });
84
+ const cyphertext = session.encrypt((0, utils_1.concatUint8Array)(crypto_1.default.hash(this.identityKey.publicKey), crypto_1.default.hash(identityKey)));
85
+ if (!cyphertext)
86
+ throw new Error();
88
87
  return {
89
- rootKey,
88
+ session,
90
89
  ackMessage: {
91
- version: X3DH.version,
92
- PK: (0, utils_1.encodeBase64)(this.PK.publicKey),
93
- IK: (0, utils_1.encodeBase64)(this.IK.publicKey),
94
- EK: (0, utils_1.encodeBase64)(EK.publicKey),
95
- SPKhash: (0, utils_1.encodeBase64)(spkHash),
96
- OPKhash: (0, utils_1.encodeBase64)(opkHash),
97
- AD: (0, utils_1.encodeBase64)(encrypter((0, utils_1.concatUint8Array)(crypto_1.default.hash(this.IK.publicKey), crypto_1.default.hash(IK)), rootKey))
90
+ version: KeyExchange.version,
91
+ publicKey: (0, utils_1.encodeBase64)(this.publicKey.publicKey),
92
+ identityKey: (0, utils_1.encodeBase64)(this.identityKey.publicKey),
93
+ ephemeralKey: (0, utils_1.encodeBase64)(ephemeralKey.publicKey),
94
+ signedPreKeyHash: (0, utils_1.encodeBase64)(signedPreKeyHash),
95
+ onetimePreKeyHash: (0, utils_1.encodeBase64)(onetimePreKeyHash),
96
+ associatedData: (0, utils_1.encodeBase64)(cyphertext.encode())
98
97
  }
99
98
  };
100
99
  }
101
- digestAck(message, verifier) {
102
- const SPK = this.bundleStore.SPK.get(message.SPKhash);
103
- const OPK = this.bundleStore.OPK.get(message.SPKhash.concat(message.OPKhash));
104
- if (!SPK || !OPK || !message.IK || !message.EK)
105
- return;
106
- const IK = (0, utils_1.decodeBase64)(message.IK);
107
- const EK = (0, utils_1.decodeBase64)(message.EK);
100
+ digestAck(message) {
101
+ const signedPreKey = this.bundleStore.get(message.signedPreKeyHash);
102
+ const hash = message.signedPreKeyHash.concat(message.onetimePreKeyHash);
103
+ const onetimePreKey = this.bundleStore.get(hash);
104
+ if (!signedPreKey || !onetimePreKey || !message.identityKey || !message.ephemeralKey)
105
+ throw new Error("ACK message malformed");
106
+ if (!this.bundleStore.delete(hash))
107
+ throw new Error("Bundle store deleting error");
108
+ const identityKey = (0, utils_1.decodeBase64)(message.identityKey);
109
+ const ephemeralKey = (0, utils_1.decodeBase64)(message.ephemeralKey);
108
110
  const rootKey = crypto_1.default.hkdf(new Uint8Array([
109
- ...crypto_1.default.scalarMult(SPK.secretKey, IK),
110
- ...crypto_1.default.scalarMult(this.IK.secretKey, EK),
111
- ...crypto_1.default.scalarMult(SPK.secretKey, EK),
112
- ...OPK ? crypto_1.default.scalarMult(OPK.secretKey, EK) : new Uint8Array()
113
- ]), new Uint8Array(double_ratchet_1.Session.rootKeyLength).fill(0), X3DH.hkdfInfo, double_ratchet_1.Session.rootKeyLength);
114
- if (!verifier)
115
- verifier = (ciphertext, key) => (0, utils_1.verifyUint8Array)(crypto_1.default.box.decrypt(ciphertext, new Uint8Array(crypto_1.default.box.nonceLength).fill(0), key), (0, utils_1.concatUint8Array)(crypto_1.default.hash(IK), crypto_1.default.hash(this.IK.publicKey)));
116
- if (!verifier((0, utils_1.decodeBase64)(message.AD), rootKey))
117
- return;
118
- return rootKey;
119
- }
120
- export() {
121
- return [
122
- this.IK,
123
- [
124
- Array.from(this.bundleStore.SPK.entries()),
125
- Array.from(this.bundleStore.OPK.entries())
126
- ]
127
- ];
128
- }
129
- static import(input) {
130
- return new X3DH(...input);
111
+ ...crypto_1.default.scalarMult(signedPreKey.secretKey, identityKey),
112
+ ...crypto_1.default.scalarMult(this.identityKey.secretKey, ephemeralKey),
113
+ ...crypto_1.default.scalarMult(signedPreKey.secretKey, ephemeralKey),
114
+ ...onetimePreKey ? crypto_1.default.scalarMult(onetimePreKey.secretKey, ephemeralKey) : new Uint8Array()
115
+ ]), new Uint8Array(double_ratchet_1.KeySession.rootKeyLength).fill(0), KeyExchange.hkdfInfo, double_ratchet_1.KeySession.rootKeyLength);
116
+ const session = new double_ratchet_1.KeySession({ secretKey: this.identityKey.secretKey, rootKey });
117
+ const cleartext = session.decrypt((0, utils_1.decodeBase64)(message.associatedData));
118
+ if (!cleartext)
119
+ throw new Error("Error decrypting ACK message");
120
+ return { session, cleartext };
131
121
  }
132
122
  }
133
- exports.X3DH = X3DH;
134
- X3DH.version = 1;
135
- X3DH.hkdfInfo = (0, utils_1.decodeUTF8)("freesignal/x3dh/" + X3DH.version);
136
- X3DH.maxOPK = 10;
123
+ exports.KeyExchange = KeyExchange;
124
+ KeyExchange.version = 1;
125
+ KeyExchange.hkdfInfo = (0, utils_1.decodeUTF8)("freesignal/x3dh/" + KeyExchange.version);
126
+ KeyExchange.maxOPK = 10;