@neykoor/libsignal-node 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +28 -41
  2. package/package.json +44 -36
  3. package/lib/base-key-type.d.ts +0 -4
  4. package/lib/base-key-type.js +0 -5
  5. package/lib/chain-type.d.ts +0 -4
  6. package/lib/chain-type.js +0 -5
  7. package/lib/crypto.d.ts +0 -6
  8. package/lib/crypto.js +0 -73
  9. package/lib/curve.d.ts +0 -9
  10. package/lib/curve.js +0 -112
  11. package/lib/direction.d.ts +0 -4
  12. package/lib/direction.js +0 -5
  13. package/lib/errors.d.ts +0 -16
  14. package/lib/errors.js +0 -28
  15. package/lib/index.d.ts +0 -20
  16. package/lib/index.js +0 -16
  17. package/lib/keyhelper.d.ts +0 -18
  18. package/lib/keyhelper.js +0 -52
  19. package/lib/logger.d.ts +0 -6
  20. package/lib/logger.js +0 -11
  21. package/lib/memory-storage.d.ts +0 -25
  22. package/lib/memory-storage.js +0 -68
  23. package/lib/numeric-fingerprint.d.ts +0 -5
  24. package/lib/numeric-fingerprint.js +0 -54
  25. package/lib/prekey-bundle-validator.d.ts +0 -2
  26. package/lib/prekey-bundle-validator.js +0 -44
  27. package/lib/protobufs.d.ts +0 -5
  28. package/lib/protobufs.js +0 -3
  29. package/lib/protocol-address.d.ts +0 -8
  30. package/lib/protocol-address.js +0 -31
  31. package/lib/queue-job.d.ts +0 -3
  32. package/lib/queue-job.js +0 -49
  33. package/lib/session-builder.d.ts +0 -12
  34. package/lib/session-builder.js +0 -153
  35. package/lib/session-cipher.d.ts +0 -29
  36. package/lib/session-cipher.js +0 -326
  37. package/lib/session-record.d.ts +0 -109
  38. package/lib/session-record.js +0 -286
  39. package/lib/types.d.ts +0 -42
  40. package/lib/types.js +0 -1
  41. package/lib/util.d.ts +0 -2
  42. package/lib/util.js +0 -10
  43. package/lib/whisper-text-protocol.d.ts +0 -3
  44. package/lib/whisper-text-protocol.js +0 -483
@@ -1,326 +0,0 @@
1
- import { ChainType } from './chain-type.js';
2
- import * as crypto from './crypto.js';
3
- import * as curve from './curve.js';
4
- import { Direction } from './direction.js';
5
- import * as errors from './errors.js';
6
- import { ProtocolAddress } from './protocol-address.js';
7
- import * as protobufs from './protobufs.js';
8
- import { queueJob } from './queue-job.js';
9
- import { wipeBuffer, wipeBuffers } from './util.js';
10
- import { SessionBuilder } from './session-builder.js';
11
- import { SessionRecord } from './session-record.js';
12
- const VERSION = 3;
13
- const DEFAULT_STALE_SESSION_MAX_AGE_MS = 90 * 24 * 60 * 60 * 1000;
14
- const MIN_MESSAGE_LENGTH = 1 + 1 + 8;
15
- function assertBuffer(value) {
16
- if (!(value instanceof Uint8Array)) {
17
- throw TypeError(`Expected Uint8Array instead of: ${value?.constructor?.name}`);
18
- }
19
- return value;
20
- }
21
- export class SessionCipher {
22
- constructor(storage, protocolAddress, staleSessionMaxAgeMs = DEFAULT_STALE_SESSION_MAX_AGE_MS) {
23
- if (!(protocolAddress instanceof ProtocolAddress)) {
24
- throw new TypeError('protocolAddress must be a ProtocolAddress');
25
- }
26
- this.addr = protocolAddress;
27
- this.storage = storage;
28
- this.staleSessionMaxAgeMs = staleSessionMaxAgeMs;
29
- }
30
- _encodeTupleByte(number1, number2) {
31
- if (number1 > 15 || number2 > 15) {
32
- throw TypeError('Numbers must be 4 bits or less');
33
- }
34
- return (number1 << 4) | number2;
35
- }
36
- _decodeTupleByte(byte) {
37
- return [byte >> 4, byte & 0xf];
38
- }
39
- toString() {
40
- return `<SessionCipher(${this.addr.toString()})>`;
41
- }
42
- async getRecord() {
43
- const record = await this.storage.loadSession(this.addr.toString());
44
- if (record && !(record instanceof SessionRecord)) {
45
- throw new TypeError('SessionRecord type expected from loadSession');
46
- }
47
- return record;
48
- }
49
- async storeRecord(record) {
50
- record.removeOldSessions();
51
- if (this.staleSessionMaxAgeMs) {
52
- record.removeStaleSessions(this.staleSessionMaxAgeMs);
53
- }
54
- await this.storage.storeSession(this.addr.toString(), record);
55
- }
56
- async queueJob(awaitable) {
57
- return await queueJob(this.addr.toString(), awaitable);
58
- }
59
- async encrypt(data) {
60
- assertBuffer(data);
61
- const ourIdentityKey = await this.storage.getOurIdentity();
62
- return await this.queueJob(async () => {
63
- const record = await this.getRecord();
64
- if (!record) {
65
- throw new errors.SessionError('No sessions');
66
- }
67
- const session = record.getOpenSession();
68
- if (!session) {
69
- throw new errors.SessionError('No open session');
70
- }
71
- const remoteIdentityKey = session.indexInfo.remoteIdentityKey;
72
- if (!(await this.storage.isTrustedIdentity(this.addr.id, remoteIdentityKey, Direction.SENDING))) {
73
- throw new errors.UntrustedIdentityKeyError(this.addr.id, remoteIdentityKey);
74
- }
75
- const chain = session.getChain(session.currentRatchet.ephemeralKeyPair.pubKey);
76
- if (!chain) {
77
- throw new errors.SessionError('No chain found for current ratchet');
78
- }
79
- if (chain.chainType === ChainType.RECEIVING) {
80
- throw new errors.SessionError('Tried to encrypt on a receiving chain');
81
- }
82
- this.fillMessageKeys(chain, chain.chainKey.counter + 1);
83
- const keys = crypto.deriveSecrets(chain.messageKeys[chain.chainKey.counter], Buffer.alloc(32), Buffer.from('WhisperMessageKeys'));
84
- wipeBuffer(chain.messageKeys[chain.chainKey.counter]);
85
- delete chain.messageKeys[chain.chainKey.counter];
86
- const msg = protobufs.WhisperMessage.create();
87
- msg.ephemeralKey = session.currentRatchet.ephemeralKeyPair.pubKey;
88
- msg.counter = chain.chainKey.counter;
89
- msg.previousCounter = session.currentRatchet.previousCounter;
90
- msg.ciphertext = crypto.encrypt(keys[0], data, keys[2].slice(0, 16));
91
- const msgBuf = protobufs.WhisperMessage.encode(msg).finish();
92
- const macInput = Buffer.alloc(msgBuf.byteLength + 33 * 2 + 1);
93
- macInput.set(ourIdentityKey.pubKey);
94
- macInput.set(session.indexInfo.remoteIdentityKey, 33);
95
- macInput[33 * 2] = this._encodeTupleByte(VERSION, VERSION);
96
- macInput.set(msgBuf, 33 * 2 + 1);
97
- const mac = crypto.calculateMAC(keys[1], macInput);
98
- wipeBuffers(keys[0], keys[1], keys[2]);
99
- const result = Buffer.alloc(msgBuf.byteLength + 9);
100
- result[0] = this._encodeTupleByte(VERSION, VERSION);
101
- result.set(msgBuf, 1);
102
- result.set(mac.slice(0, 8), msgBuf.byteLength + 1);
103
- await this.storeRecord(record);
104
- let type;
105
- let body;
106
- if (session.pendingPreKey) {
107
- type = 3;
108
- const preKeyMsg = protobufs.PreKeyWhisperMessage.create({
109
- identityKey: ourIdentityKey.pubKey,
110
- registrationId: await this.storage.getOurRegistrationId(),
111
- baseKey: session.pendingPreKey.baseKey,
112
- signedPreKeyId: session.pendingPreKey.signedKeyId,
113
- message: result
114
- });
115
- if (session.pendingPreKey.preKeyId) {
116
- preKeyMsg.preKeyId = session.pendingPreKey.preKeyId;
117
- }
118
- body = Buffer.concat([
119
- Buffer.from([this._encodeTupleByte(VERSION, VERSION)]),
120
- Buffer.from(protobufs.PreKeyWhisperMessage.encode(preKeyMsg).finish())
121
- ]);
122
- }
123
- else {
124
- type = 1;
125
- body = result;
126
- }
127
- return {
128
- type,
129
- body,
130
- registrationId: session.registrationId
131
- };
132
- });
133
- }
134
- async decryptWithSessions(data, sessions) {
135
- if (!sessions.length) {
136
- throw new errors.SessionError('No sessions available');
137
- }
138
- const errs = [];
139
- for (const session of sessions) {
140
- let plaintext;
141
- try {
142
- plaintext = await this.doDecryptWhisperMessage(data, session);
143
- session.indexInfo.used = Date.now();
144
- return {
145
- session,
146
- plaintext
147
- };
148
- }
149
- catch (e) {
150
- errs.push(e);
151
- }
152
- }
153
- throw Object.assign(new errors.SessionError('No matching sessions found for message'), { errors: errs });
154
- }
155
- async decryptWhisperMessage(data) {
156
- assertBuffer(data);
157
- return await this.queueJob(async () => {
158
- const record = await this.getRecord();
159
- if (!record) {
160
- throw new errors.SessionError('No session record');
161
- }
162
- const result = await this.decryptWithSessions(data, record.getSessions());
163
- const remoteIdentityKey = result.session.indexInfo.remoteIdentityKey;
164
- if (!(await this.storage.isTrustedIdentity(this.addr.id, remoteIdentityKey, Direction.RECEIVING))) {
165
- throw new errors.UntrustedIdentityKeyError(this.addr.id, remoteIdentityKey);
166
- }
167
- if (record.isClosed(result.session)) {
168
- result.session.indexInfo.used = Date.now();
169
- }
170
- await this.storeRecord(record);
171
- return result.plaintext;
172
- });
173
- }
174
- async decryptPreKeyWhisperMessage(data) {
175
- assertBuffer(data);
176
- if (data.byteLength < MIN_MESSAGE_LENGTH) {
177
- throw new errors.SessionError('Message too short');
178
- }
179
- const versions = this._decodeTupleByte(data[0]);
180
- if (versions[1] > 3 || versions[0] < 3) {
181
- throw new Error('Incompatible version number on PreKeyWhisperMessage');
182
- }
183
- return await this.queueJob(async () => {
184
- let record = await this.getRecord();
185
- const preKeyProto = protobufs.PreKeyWhisperMessage.decode(data.slice(1));
186
- if (!record) {
187
- if (preKeyProto.registrationId == null) {
188
- throw new errors.PreKeyError('No registrationId');
189
- }
190
- record = new SessionRecord();
191
- }
192
- const builder = new SessionBuilder(this.storage, this.addr);
193
- const preKeyId = await builder.initIncoming(record, {
194
- registrationId: preKeyProto.registrationId,
195
- identityKey: Buffer.from(preKeyProto.identityKey),
196
- baseKey: Buffer.from(preKeyProto.baseKey),
197
- preKeyId: preKeyProto.preKeyId,
198
- signedPreKeyId: preKeyProto.signedPreKeyId,
199
- message: Buffer.from(preKeyProto.message)
200
- });
201
- const session = record.getSession(Buffer.from(preKeyProto.baseKey));
202
- if (!session) {
203
- throw new errors.SessionError('No session established for incoming PreKeyWhisperMessage');
204
- }
205
- const plaintext = await this.doDecryptWhisperMessage(Buffer.from(preKeyProto.message), session);
206
- await this.storeRecord(record);
207
- if (preKeyId !== undefined) {
208
- await this.storage.removePreKey(preKeyId);
209
- }
210
- return plaintext;
211
- });
212
- }
213
- async doDecryptWhisperMessage(messageBuffer, session) {
214
- assertBuffer(messageBuffer);
215
- if (!session) {
216
- throw new TypeError('session required');
217
- }
218
- if (messageBuffer.byteLength < MIN_MESSAGE_LENGTH) {
219
- throw new errors.SessionError('Message too short');
220
- }
221
- const versions = this._decodeTupleByte(messageBuffer[0]);
222
- if (versions[1] > 3 || versions[0] < 3) {
223
- throw new Error('Incompatible version number on WhisperMessage');
224
- }
225
- const messageProto = messageBuffer.slice(1, -8);
226
- const message = protobufs.WhisperMessage.decode(messageProto);
227
- this.maybeStepRatchet(session, Buffer.from(message.ephemeralKey), message.previousCounter);
228
- const chain = session.getChain(Buffer.from(message.ephemeralKey));
229
- if (!chain) {
230
- throw new errors.SessionError('No chain found for message ephemeral key');
231
- }
232
- if (chain.chainType === ChainType.SENDING) {
233
- throw new errors.SessionError('Tried to decrypt on a sending chain');
234
- }
235
- this.fillMessageKeys(chain, message.counter);
236
- if (!Object.prototype.hasOwnProperty.call(chain.messageKeys, message.counter)) {
237
- throw new errors.MessageCounterError('Key used already or never filled');
238
- }
239
- const messageKey = chain.messageKeys[message.counter];
240
- delete chain.messageKeys[message.counter];
241
- const keys = crypto.deriveSecrets(messageKey, Buffer.alloc(32), Buffer.from('WhisperMessageKeys'));
242
- wipeBuffer(messageKey);
243
- const ourIdentityKey = await this.storage.getOurIdentity();
244
- const macInput = Buffer.alloc(messageProto.byteLength + 33 * 2 + 1);
245
- macInput.set(session.indexInfo.remoteIdentityKey);
246
- macInput.set(ourIdentityKey.pubKey, 33);
247
- macInput[33 * 2] = this._encodeTupleByte(VERSION, VERSION);
248
- macInput.set(messageProto, 33 * 2 + 1);
249
- crypto.verifyMAC(macInput, keys[1], messageBuffer.slice(-8), 8);
250
- const plaintext = crypto.decrypt(keys[0], Buffer.from(message.ciphertext), keys[2].slice(0, 16));
251
- wipeBuffers(keys[0], keys[1], keys[2]);
252
- delete session.pendingPreKey;
253
- return plaintext;
254
- }
255
- fillMessageKeys(chain, counter) {
256
- while (chain.chainKey.counter < counter) {
257
- if (counter - chain.chainKey.counter > 2000) {
258
- throw new errors.SessionError('Over 2000 messages into the future!');
259
- }
260
- if (chain.chainKey.key === undefined) {
261
- throw new errors.SessionError('Chain closed');
262
- }
263
- const key = chain.chainKey.key;
264
- chain.messageKeys[chain.chainKey.counter + 1] = crypto.calculateMAC(key, Buffer.from([1]));
265
- chain.chainKey.key = crypto.calculateMAC(key, Buffer.from([2]));
266
- chain.chainKey.counter += 1;
267
- }
268
- }
269
- maybeStepRatchet(session, remoteKey, previousCounter) {
270
- if (session.getChain(remoteKey)) {
271
- return;
272
- }
273
- const ratchet = session.currentRatchet;
274
- const previousRatchet = session.getChain(ratchet.lastRemoteEphemeralKey);
275
- if (previousRatchet) {
276
- this.fillMessageKeys(previousRatchet, previousCounter);
277
- wipeBuffer(previousRatchet.chainKey.key);
278
- delete previousRatchet.chainKey.key;
279
- }
280
- this.calculateRatchet(session, remoteKey, false);
281
- const prevCounter = session.getChain(ratchet.ephemeralKeyPair.pubKey);
282
- if (prevCounter) {
283
- ratchet.previousCounter = prevCounter.chainKey.counter;
284
- session.deleteChain(ratchet.ephemeralKeyPair.pubKey);
285
- }
286
- ratchet.ephemeralKeyPair = curve.generateKeyPair();
287
- this.calculateRatchet(session, remoteKey, true);
288
- ratchet.lastRemoteEphemeralKey = remoteKey;
289
- }
290
- calculateRatchet(session, remoteKey, sending) {
291
- const ratchet = session.currentRatchet;
292
- const sharedSecret = curve.calculateAgreement(remoteKey, ratchet.ephemeralKeyPair.privKey);
293
- const masterKey = crypto.deriveSecrets(sharedSecret, ratchet.rootKey, Buffer.from('WhisperRatchet'), 2);
294
- const chainKey = sending ? ratchet.ephemeralKeyPair.pubKey : remoteKey;
295
- session.addChain(chainKey, {
296
- messageKeys: {},
297
- chainKey: {
298
- counter: -1,
299
- key: masterKey[1]
300
- },
301
- chainType: sending ? ChainType.SENDING : ChainType.RECEIVING
302
- });
303
- ratchet.rootKey = masterKey[0];
304
- }
305
- async hasOpenSession() {
306
- return await this.queueJob(async () => {
307
- const record = await this.getRecord();
308
- if (!record) {
309
- return false;
310
- }
311
- return record.haveOpenSession();
312
- });
313
- }
314
- async closeOpenSession() {
315
- return await this.queueJob(async () => {
316
- const record = await this.getRecord();
317
- if (record) {
318
- const openSession = record.getOpenSession();
319
- if (openSession) {
320
- record.closeSession(openSession);
321
- await this.storeRecord(record);
322
- }
323
- }
324
- });
325
- }
326
- }
@@ -1,109 +0,0 @@
1
- import { BaseKeyType } from './base-key-type.js';
2
- import type { ChainType } from './chain-type.js';
3
- export interface ChainKey {
4
- counter: number;
5
- key?: Buffer;
6
- }
7
- export interface Chain {
8
- messageKeys: Record<number, Buffer>;
9
- chainKey: ChainKey;
10
- chainType: ChainType;
11
- }
12
- export interface CurrentRatchet {
13
- ephemeralKeyPair: {
14
- pubKey: Buffer;
15
- privKey: Buffer;
16
- };
17
- lastRemoteEphemeralKey: Buffer;
18
- previousCounter: number;
19
- rootKey: Buffer;
20
- }
21
- export interface IndexInfo {
22
- baseKey: Buffer;
23
- baseKeyType: BaseKeyType;
24
- closed: number;
25
- used: number;
26
- created: number;
27
- remoteIdentityKey: Buffer;
28
- }
29
- export interface PendingPreKey {
30
- signedKeyId: number;
31
- baseKey: Buffer;
32
- preKeyId?: number;
33
- }
34
- interface SerializedChain {
35
- chainKey: {
36
- counter: number;
37
- key?: string;
38
- };
39
- chainType: ChainType;
40
- messageKeys: Record<number, string>;
41
- }
42
- export interface SerializedSessionEntry {
43
- registrationId?: number;
44
- currentRatchet: {
45
- ephemeralKeyPair: {
46
- pubKey: string;
47
- privKey: string;
48
- };
49
- lastRemoteEphemeralKey: string;
50
- previousCounter: number;
51
- rootKey: string;
52
- };
53
- indexInfo: {
54
- baseKey: string;
55
- baseKeyType: BaseKeyType;
56
- closed: number;
57
- used: number;
58
- created: number;
59
- remoteIdentityKey: string;
60
- };
61
- _chains: Record<string, SerializedChain>;
62
- pendingPreKey?: {
63
- signedKeyId: number;
64
- baseKey: string;
65
- preKeyId?: number;
66
- };
67
- }
68
- export declare class SessionEntry {
69
- registrationId?: number;
70
- currentRatchet: CurrentRatchet;
71
- indexInfo: IndexInfo;
72
- pendingPreKey?: PendingPreKey;
73
- private _chains;
74
- toString(): string;
75
- inspect(): string;
76
- addChain(key: Buffer, value: Chain): void;
77
- getChain(key: Buffer): Chain | undefined;
78
- deleteChain(key: Buffer): void;
79
- chains(): IterableIterator<[Buffer, Chain]>;
80
- serialize(): SerializedSessionEntry;
81
- static deserialize(data: SerializedSessionEntry): SessionEntry;
82
- private static _serializeChains;
83
- private static _deserializeChains;
84
- }
85
- export interface SerializedSessionRecord {
86
- version?: string;
87
- registrationId?: number;
88
- _sessions: Record<string, SerializedSessionEntry>;
89
- }
90
- export declare class SessionRecord {
91
- sessions: Record<string, SessionEntry>;
92
- version: string;
93
- static createEntry(): SessionEntry;
94
- static migrate(data: SerializedSessionRecord): void;
95
- static deserialize(data: Uint8Array): SessionRecord;
96
- serialize(): Buffer;
97
- haveOpenSession(): boolean;
98
- getSession(key: Buffer): SessionEntry | undefined;
99
- getOpenSession(): SessionEntry | undefined;
100
- setSession(session: SessionEntry): void;
101
- getSessions(): SessionEntry[];
102
- closeSession(session: SessionEntry): void;
103
- openSession(session: SessionEntry): void;
104
- isClosed(session: SessionEntry): boolean;
105
- removeOldSessions(): void;
106
- removeStaleSessions(maxAgeMs: number): void;
107
- deleteAllSessions(): void;
108
- }
109
- export {};