@unknownncat/swt-libsignal 1.0.5 → 1.0.7
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/LICENSE +21 -0
- package/dist/crypto.d.ts +1 -1
- package/dist/crypto.js +69 -35
- package/dist/curve.d.ts +1 -1
- package/dist/fingerprint.js +1 -1
- package/dist/index.d.ts +19 -20
- package/dist/index.js +12 -13
- package/dist/job_queue.js +22 -18
- package/dist/key-helper.d.ts +1 -1
- package/dist/key-helper.js +1 -1
- package/dist/protobuf.d.ts +1 -1
- package/dist/protobuf.js +1 -1
- package/dist/{base_key_type.d.ts → ratchet-types.d.ts} +5 -0
- package/dist/{chain_type.js → ratchet-types.js} +4 -0
- package/dist/session/builder/index.d.ts +2 -2
- package/dist/session/builder/index.js +2 -2
- package/dist/session/builder/session-builder.d.ts +3 -3
- package/dist/session/builder/session-builder.js +36 -16
- package/dist/session/builder/types.d.ts +1 -1
- package/dist/session/cipher/encoding.d.ts +1 -1
- package/dist/session/cipher/index.d.ts +3 -3
- package/dist/session/cipher/index.js +2 -3
- package/dist/session/cipher/session-cipher.d.ts +2 -2
- package/dist/session/cipher/session-cipher.js +108 -59
- package/dist/session/cipher/types.d.ts +2 -2
- package/dist/session/index.d.ts +5 -3
- package/dist/session/index.js +5 -3
- package/dist/session/record/index.d.ts +3 -3
- package/dist/session/record/index.js +3 -3
- package/dist/session/record/session-entry.d.ts +1 -1
- package/dist/session/record/session-entry.js +2 -3
- package/dist/session/record/session-record.d.ts +2 -2
- package/dist/session/record/session-record.js +4 -4
- package/dist/session/record/types.d.ts +3 -3
- package/dist/session/storage/adapter.d.ts +21 -0
- package/dist/session/storage/adapter.js +57 -0
- package/dist/session/storage/in-memory.d.ts +13 -0
- package/dist/session/storage/in-memory.js +33 -0
- package/dist/session/storage/index.d.ts +19 -0
- package/dist/session/storage/index.js +29 -0
- package/dist/session/storage/migrations.d.ts +2 -0
- package/dist/session/storage/migrations.js +6 -0
- package/dist/session/storage/types.d.ts +18 -0
- package/dist/types/asymmetric.d.ts +41 -0
- package/dist/types/crypto.d.ts +69 -0
- package/dist/types/index.d.ts +2 -0
- package/package.json +12 -3
- package/dist/base_key_type.js +0 -4
- package/dist/chain_type.d.ts +0 -5
- package/dist/teste.js +0 -18
- /package/dist/{teste.d.ts → session/storage/types.js} +0 -0
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { PROTOCOL_VERSION } from '../constants
|
|
2
|
-
import { WhisperMessageEncoder } from './encoding
|
|
3
|
-
import { assertUint8 } from '../utils
|
|
4
|
-
import { ChainType } from
|
|
5
|
-
import { ProtocolAddress } from '../../protocol_address
|
|
6
|
-
import { SessionBuilder } from '../builder/session-builder
|
|
7
|
-
import { SessionRecord } from '../record/index
|
|
8
|
-
import { crypto } from '../../crypto
|
|
9
|
-
import { signalCrypto } from '../../curve
|
|
10
|
-
import { SessionError, UntrustedIdentityKeyError, MessageCounterError } from '../../signal-errors
|
|
11
|
-
import { enqueue } from '../../job_queue
|
|
1
|
+
import { PROTOCOL_VERSION } from '../constants';
|
|
2
|
+
import { WhisperMessageEncoder } from './encoding';
|
|
3
|
+
import { assertUint8, toBase64 } from '../utils';
|
|
4
|
+
import { ChainType } from "../../ratchet-types";
|
|
5
|
+
import { ProtocolAddress } from '../../protocol_address';
|
|
6
|
+
import { SessionBuilder } from '../builder/session-builder';
|
|
7
|
+
import { SessionRecord } from '../record/index';
|
|
8
|
+
import { crypto } from '../../crypto';
|
|
9
|
+
import { signalCrypto } from '../../curve';
|
|
10
|
+
import { SessionError, UntrustedIdentityKeyError, MessageCounterError } from '../../signal-errors';
|
|
11
|
+
import { enqueue } from '../../job_queue';
|
|
12
12
|
export class SessionCipher {
|
|
13
13
|
addr;
|
|
14
14
|
addrStr;
|
|
@@ -69,45 +69,54 @@ export class SessionCipher {
|
|
|
69
69
|
if (!messageKey)
|
|
70
70
|
throw new Error('Message key not generated');
|
|
71
71
|
const keys = this.deriveSecrets(messageKey, new Uint8Array(32), new TextEncoder().encode('WhisperMessageKeys'));
|
|
72
|
-
delete chain.messageKeys[chain.chainKey.counter];
|
|
73
72
|
const [cipherKey, macKey, aadKey] = keys;
|
|
74
|
-
if (!cipherKey || !macKey || !aadKey)
|
|
75
|
-
throw new Error('
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
macInput.set(ourIdentity.pubKey);
|
|
86
|
-
macInput.set(remoteIdentityKey, 33);
|
|
87
|
-
macInput[66] = this._encodeTupleByte(PROTOCOL_VERSION, PROTOCOL_VERSION);
|
|
88
|
-
macInput.set(msgBuf, 67);
|
|
89
|
-
const mac = crypto.hmacSha256(macKey, macInput);
|
|
90
|
-
const result = new Uint8Array(msgBuf.byteLength + 9);
|
|
91
|
-
result[0] = this._encodeTupleByte(PROTOCOL_VERSION, PROTOCOL_VERSION);
|
|
92
|
-
result.set(msgBuf, 1);
|
|
93
|
-
result.set(mac.subarray(0, 8), msgBuf.byteLength + 1);
|
|
94
|
-
await this.storeRecord(record);
|
|
95
|
-
if (session.pendingPreKey) {
|
|
96
|
-
const preKeyMsg = {
|
|
97
|
-
identityKey: ourIdentity.pubKey,
|
|
98
|
-
registrationId: await this.storage.getOurRegistrationId(),
|
|
99
|
-
baseKey: session.pendingPreKey.baseKey,
|
|
100
|
-
signedPreKeyId: session.pendingPreKey.signedKeyId,
|
|
101
|
-
preKeyId: session.pendingPreKey.preKeyId,
|
|
102
|
-
message: result
|
|
73
|
+
if (!cipherKey || !macKey || !aadKey || cipherKey.length !== 32 || macKey.length !== 32 || aadKey.length !== 32) {
|
|
74
|
+
throw new Error('Invalid key derivation');
|
|
75
|
+
}
|
|
76
|
+
let result;
|
|
77
|
+
try {
|
|
78
|
+
const encrypted = await crypto.encrypt(cipherKey, data, { aad: aadKey.subarray(0, 16) });
|
|
79
|
+
const msg = {
|
|
80
|
+
ephemeralKey: session.currentRatchet.ephemeralKeyPair.pubKey,
|
|
81
|
+
counter: chain.chainKey.counter,
|
|
82
|
+
previousCounter: session.currentRatchet.previousCounter,
|
|
83
|
+
ciphertext: encrypted.ciphertext
|
|
103
84
|
};
|
|
104
|
-
const
|
|
105
|
-
const
|
|
85
|
+
const msgBuf = WhisperMessageEncoder.encodeWhisperMessage(msg);
|
|
86
|
+
const macInput = new Uint8Array(msgBuf.byteLength + 67);
|
|
87
|
+
macInput.set(ourIdentity.pubKey);
|
|
88
|
+
macInput.set(remoteIdentityKey, 33);
|
|
89
|
+
macInput[66] = this._encodeTupleByte(PROTOCOL_VERSION, PROTOCOL_VERSION);
|
|
90
|
+
macInput.set(msgBuf, 67);
|
|
91
|
+
const mac = crypto.hmacSha256(macKey, macInput);
|
|
92
|
+
const body = new Uint8Array(msgBuf.byteLength + 9);
|
|
106
93
|
body[0] = this._encodeTupleByte(PROTOCOL_VERSION, PROTOCOL_VERSION);
|
|
107
|
-
body.set(
|
|
108
|
-
|
|
94
|
+
body.set(msgBuf, 1);
|
|
95
|
+
body.set(mac.subarray(0, 8), msgBuf.byteLength + 1);
|
|
96
|
+
if (session.pendingPreKey) {
|
|
97
|
+
const preKeyMsg = {
|
|
98
|
+
identityKey: ourIdentity.pubKey,
|
|
99
|
+
registrationId: await this.storage.getOurRegistrationId(),
|
|
100
|
+
baseKey: session.pendingPreKey.baseKey,
|
|
101
|
+
signedPreKeyId: session.pendingPreKey.signedKeyId,
|
|
102
|
+
preKeyId: session.pendingPreKey.preKeyId,
|
|
103
|
+
message: body
|
|
104
|
+
};
|
|
105
|
+
const preKeyBuf = WhisperMessageEncoder.encodePreKeyWhisperMessage(preKeyMsg);
|
|
106
|
+
const finalBody = new Uint8Array(1 + preKeyBuf.byteLength);
|
|
107
|
+
finalBody[0] = this._encodeTupleByte(PROTOCOL_VERSION, PROTOCOL_VERSION);
|
|
108
|
+
finalBody.set(preKeyBuf, 1);
|
|
109
|
+
result = { type: 3, body: finalBody, registrationId: session.registrationId };
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
result = { type: 1, body, registrationId: session.registrationId };
|
|
113
|
+
}
|
|
109
114
|
}
|
|
110
|
-
|
|
115
|
+
finally {
|
|
116
|
+
delete chain.messageKeys[chain.chainKey.counter];
|
|
117
|
+
}
|
|
118
|
+
await this.storeRecord(record);
|
|
119
|
+
return result;
|
|
111
120
|
});
|
|
112
121
|
}
|
|
113
122
|
async decryptWhisperMessage(data) {
|
|
@@ -136,9 +145,22 @@ export class SessionCipher {
|
|
|
136
145
|
return this.queueJob(async () => {
|
|
137
146
|
let record = await this.getRecord();
|
|
138
147
|
const preKeyProto = WhisperMessageEncoder.decodePreKeyWhisperMessage(data.subarray(1));
|
|
148
|
+
if (!preKeyProto.identityKey || preKeyProto.identityKey.length === 0) {
|
|
149
|
+
throw new Error('Missing or empty identityKey in PreKeyWhisperMessage');
|
|
150
|
+
}
|
|
151
|
+
if (!preKeyProto.baseKey || preKeyProto.baseKey.length === 0) {
|
|
152
|
+
throw new Error('Missing or empty baseKey in PreKeyWhisperMessage');
|
|
153
|
+
}
|
|
154
|
+
if (!preKeyProto.message || preKeyProto.message.length === 0) {
|
|
155
|
+
throw new Error('Missing or empty message in PreKeyWhisperMessage');
|
|
156
|
+
}
|
|
157
|
+
if (preKeyProto.signedPreKeyId == null) {
|
|
158
|
+
throw new Error('Missing signedPreKeyId in PreKeyWhisperMessage');
|
|
159
|
+
}
|
|
160
|
+
if (preKeyProto.registrationId == null) {
|
|
161
|
+
throw new Error('Missing registrationId in PreKeyWhisperMessage');
|
|
162
|
+
}
|
|
139
163
|
if (!record) {
|
|
140
|
-
if (preKeyProto.registrationId == null)
|
|
141
|
-
throw new Error('No registrationId');
|
|
142
164
|
record = new SessionRecord();
|
|
143
165
|
}
|
|
144
166
|
const builder = new SessionBuilder(this.storage, this.addr);
|
|
@@ -175,19 +197,25 @@ export class SessionCipher {
|
|
|
175
197
|
if (!sessions.length)
|
|
176
198
|
throw new SessionError('No sessions available');
|
|
177
199
|
const errors = [];
|
|
178
|
-
for (
|
|
200
|
+
for (let i = 0; i < sessions.length; i++) {
|
|
201
|
+
const session = sessions[i];
|
|
202
|
+
const sessionKey = toBase64(session.indexInfo.baseKey);
|
|
179
203
|
try {
|
|
180
204
|
const plaintext = await this.doDecryptWhisperMessage(data, session);
|
|
181
205
|
session.indexInfo.used = Date.now();
|
|
182
206
|
return { session, plaintext };
|
|
183
207
|
}
|
|
184
208
|
catch (e) {
|
|
185
|
-
errors.push(
|
|
209
|
+
errors.push({
|
|
210
|
+
sessionKey,
|
|
211
|
+
error: e instanceof Error ? e : new Error(String(e))
|
|
212
|
+
});
|
|
186
213
|
}
|
|
187
214
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
215
|
+
const errorDetails = errors
|
|
216
|
+
.map(({ sessionKey, error }) => `[${sessionKey}]: ${error.message}`)
|
|
217
|
+
.join(' | ');
|
|
218
|
+
throw new SessionError(`No matching sessions found for message. Tried ${sessions.length} sessions. Errors: ${errorDetails}`, { cause: errors[0]?.error });
|
|
191
219
|
}
|
|
192
220
|
async doDecryptWhisperMessage(messageBuffer, session) {
|
|
193
221
|
assertUint8(messageBuffer);
|
|
@@ -231,8 +259,13 @@ export class SessionCipher {
|
|
|
231
259
|
fillMessageKeys(chain, targetCounter) {
|
|
232
260
|
if (chain.chainKey.counter >= targetCounter)
|
|
233
261
|
return;
|
|
234
|
-
|
|
235
|
-
|
|
262
|
+
const maxFutureMessages = 2000;
|
|
263
|
+
const newMessages = targetCounter - chain.chainKey.counter;
|
|
264
|
+
if (newMessages > maxFutureMessages) {
|
|
265
|
+
throw new SessionError(`Over ${maxFutureMessages} messages into the future: ${newMessages} messages`);
|
|
266
|
+
}
|
|
267
|
+
if (targetCounter > Number.MAX_SAFE_INTEGER - 1000) {
|
|
268
|
+
throw new SessionError(`Counter would overflow: current=${chain.chainKey.counter}, target=${targetCounter}`);
|
|
236
269
|
}
|
|
237
270
|
if (chain.chainKey.key === undefined) {
|
|
238
271
|
throw new SessionError('Chain closed');
|
|
@@ -283,18 +316,34 @@ export class SessionCipher {
|
|
|
283
316
|
ratchet.rootKey = masterKeys[0];
|
|
284
317
|
}
|
|
285
318
|
deriveSecrets(input, salt, info, chunks = 3) {
|
|
286
|
-
const
|
|
319
|
+
const expectedLength = chunks * 32;
|
|
320
|
+
const hkdf = crypto.hkdf(input, salt, info, { length: expectedLength });
|
|
321
|
+
if (hkdf.length !== expectedLength) {
|
|
322
|
+
throw new Error(`HKDF derivation failed: expected ${expectedLength} bytes, got ${hkdf.length}`);
|
|
323
|
+
}
|
|
287
324
|
const result = [];
|
|
288
325
|
for (let i = 0; i < chunks; i++) {
|
|
289
|
-
|
|
326
|
+
const key = hkdf.subarray(i * 32, (i + 1) * 32);
|
|
327
|
+
if (key.length !== 32) {
|
|
328
|
+
throw new Error(`Invalid key derivation at chunk ${i}: expected 32 bytes, got ${key.length}`);
|
|
329
|
+
}
|
|
330
|
+
result.push(key);
|
|
290
331
|
}
|
|
291
332
|
return result;
|
|
292
333
|
}
|
|
293
334
|
verifyMAC(macInput, key, mac, length) {
|
|
335
|
+
if (mac.length < length) {
|
|
336
|
+
throw new Error('MAC too short');
|
|
337
|
+
}
|
|
294
338
|
const computed = crypto.hmacSha256(key, macInput);
|
|
339
|
+
let match = true;
|
|
295
340
|
for (let i = 0; i < length; i++) {
|
|
296
|
-
if (computed[i] !== mac[i])
|
|
297
|
-
|
|
341
|
+
if (computed[i] !== mac[i]) {
|
|
342
|
+
match = false;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
if (!match) {
|
|
346
|
+
throw new Error('MAC verification failed');
|
|
298
347
|
}
|
|
299
348
|
}
|
|
300
349
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { SessionEntry } from "../record/session-entry
|
|
2
|
-
import type { SessionRecord } from "../record/session-record
|
|
1
|
+
import type { SessionEntry } from "../record/session-entry";
|
|
2
|
+
import type { SessionRecord } from "../record/session-record";
|
|
3
3
|
export interface EncryptResult {
|
|
4
4
|
type: number;
|
|
5
5
|
body: Uint8Array;
|
package/dist/session/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
export
|
|
2
|
-
export * from "./
|
|
3
|
-
export * from "./
|
|
1
|
+
export { WhisperMessageEncoder } from "./cipher/index";
|
|
2
|
+
export * from "./cipher/index";
|
|
3
|
+
export * from "./record/index";
|
|
4
|
+
export * from "./builder/index";
|
|
5
|
+
export * from "./storage/index";
|
package/dist/session/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
export
|
|
2
|
-
export * from "./
|
|
3
|
-
export * from "./
|
|
1
|
+
export { WhisperMessageEncoder } from "./cipher/index";
|
|
2
|
+
export * from "./cipher/index";
|
|
3
|
+
export * from "./record/index";
|
|
4
|
+
export * from "./builder/index";
|
|
5
|
+
export * from "./storage/index";
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export * from './types
|
|
2
|
-
export * from './session-entry
|
|
3
|
-
export * from './session-record
|
|
1
|
+
export * from './types';
|
|
2
|
+
export * from './session-entry';
|
|
3
|
+
export * from './session-record';
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export * from './types
|
|
2
|
-
export * from './session-entry
|
|
3
|
-
export * from './session-record
|
|
1
|
+
export * from './types';
|
|
2
|
+
export * from './session-entry';
|
|
3
|
+
export * from './session-record';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ChainState, CurrentRatchet, IndexInfo, PendingPreKey, SerializedSessionEntry } from './types
|
|
1
|
+
import type { ChainState, CurrentRatchet, IndexInfo, PendingPreKey, SerializedSessionEntry } from './types';
|
|
2
2
|
export declare class SessionEntry {
|
|
3
3
|
registrationId: number;
|
|
4
4
|
currentRatchet: CurrentRatchet;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { assertUint8, toBase64, u8 } from '../utils
|
|
1
|
+
import { assertUint8, toBase64, fromBase64, u8 } from '../utils';
|
|
2
2
|
export class SessionEntry {
|
|
3
3
|
registrationId;
|
|
4
4
|
currentRatchet;
|
|
@@ -113,7 +113,7 @@ export class SessionEntry {
|
|
|
113
113
|
return {
|
|
114
114
|
baseKey: u8.decode(data.baseKey),
|
|
115
115
|
signedKeyId: data.signedKeyId,
|
|
116
|
-
preKeyId: data.preKeyId,
|
|
116
|
+
...(data.preKeyId !== undefined && { preKeyId: data.preKeyId }),
|
|
117
117
|
};
|
|
118
118
|
}
|
|
119
119
|
static deserialize(data) {
|
|
@@ -143,4 +143,3 @@ export class SessionEntry {
|
|
|
143
143
|
return obj;
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
|
-
import { fromBase64 } from '../utils.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { SerializedSessionRecord } from './types
|
|
2
|
-
import { SessionEntry } from './session-entry
|
|
1
|
+
import type { SerializedSessionRecord } from './types';
|
|
2
|
+
import { SessionEntry } from './session-entry';
|
|
3
3
|
export declare class SessionRecord {
|
|
4
4
|
sessions: Record<string, SessionEntry>;
|
|
5
5
|
version: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { SessionEntry } from './session-entry
|
|
2
|
-
import { CLOSED_SESSIONS_MAX, SESSION_RECORD_VERSION } from '../constants
|
|
3
|
-
import { assertUint8, toBase64 } from '../utils
|
|
4
|
-
import { BaseKeyType } from
|
|
1
|
+
import { SessionEntry } from './session-entry';
|
|
2
|
+
import { CLOSED_SESSIONS_MAX, SESSION_RECORD_VERSION } from '../constants';
|
|
3
|
+
import { assertUint8, toBase64 } from '../utils';
|
|
4
|
+
import { BaseKeyType } from "../../ratchet-types";
|
|
5
5
|
export class SessionRecord {
|
|
6
6
|
sessions = {};
|
|
7
7
|
version = SESSION_RECORD_VERSION;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BaseKeyType } from "../../
|
|
1
|
+
import type { BaseKeyType, ChainType } from "../../ratchet-types";
|
|
2
2
|
export interface PendingPreKey {
|
|
3
3
|
baseKey: Uint8Array;
|
|
4
4
|
signedKeyId: number;
|
|
@@ -10,7 +10,7 @@ export interface ChainKey {
|
|
|
10
10
|
}
|
|
11
11
|
export interface ChainState {
|
|
12
12
|
chainKey: ChainKey;
|
|
13
|
-
chainType:
|
|
13
|
+
chainType: ChainType;
|
|
14
14
|
messageKeys: Record<string, Uint8Array>;
|
|
15
15
|
}
|
|
16
16
|
export interface CurrentRatchet {
|
|
@@ -35,7 +35,7 @@ export interface SerializedChainState {
|
|
|
35
35
|
counter: number;
|
|
36
36
|
key: string | undefined;
|
|
37
37
|
};
|
|
38
|
-
chainType:
|
|
38
|
+
chainType: ChainType;
|
|
39
39
|
messageKeys: Record<string, string>;
|
|
40
40
|
}
|
|
41
41
|
export interface SerializedPendingPreKey {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { StorageAdapter } from './types';
|
|
2
|
+
import { SessionRecord } from '../record/index';
|
|
3
|
+
export declare function createSessionStorage(adapter: StorageAdapter): {
|
|
4
|
+
isTrustedIdentity(addressName: string, identityKey: Uint8Array): Promise<boolean>;
|
|
5
|
+
loadSession(addressName: string): Promise<SessionRecord | undefined>;
|
|
6
|
+
storeSession(addressName: string, record: ReturnType<(typeof SessionRecord)["prototype"]["serialize"]>): Promise<void>;
|
|
7
|
+
getOurIdentity(): Promise<{
|
|
8
|
+
pubKey: Uint8Array<ArrayBufferLike>;
|
|
9
|
+
privKey: Uint8Array<ArrayBufferLike>;
|
|
10
|
+
}>;
|
|
11
|
+
loadPreKey(preKeyId: number): Promise<{
|
|
12
|
+
pubKey: Uint8Array<ArrayBufferLike>;
|
|
13
|
+
privKey: Uint8Array<ArrayBufferLike>;
|
|
14
|
+
} | undefined>;
|
|
15
|
+
loadSignedPreKey(signedPreKeyId: number): Promise<{
|
|
16
|
+
pubKey: Uint8Array<ArrayBufferLike>;
|
|
17
|
+
privKey: Uint8Array<ArrayBufferLike>;
|
|
18
|
+
} | undefined>;
|
|
19
|
+
removePreKey(preKeyId: number): Promise<void>;
|
|
20
|
+
getOurRegistrationId(): Promise<number>;
|
|
21
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { SessionRecord } from '../record/index';
|
|
2
|
+
import { toBase64, fromBase64 } from '../utils';
|
|
3
|
+
function sessionKey(addr) { return `session:${addr}`; }
|
|
4
|
+
function preKeyKey(id) { return `prekey:${id}`; }
|
|
5
|
+
function signedPreKeyKey(id) { return `signedprekey:${id}`; }
|
|
6
|
+
function identityKeyF(addr) { return `identity:${addr}`; }
|
|
7
|
+
const OUR_IDENTITY = 'our_identity';
|
|
8
|
+
const REG_ID = 'registration_id';
|
|
9
|
+
export function createSessionStorage(adapter) {
|
|
10
|
+
return {
|
|
11
|
+
async isTrustedIdentity(addressName, identityKey) {
|
|
12
|
+
const stored = await adapter.get(identityKeyF(addressName));
|
|
13
|
+
const encoded = toBase64(identityKey);
|
|
14
|
+
if (!stored) {
|
|
15
|
+
await adapter.set(identityKeyF(addressName), encoded);
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
return stored === encoded;
|
|
19
|
+
},
|
|
20
|
+
async loadSession(addressName) {
|
|
21
|
+
const data = await adapter.get(sessionKey(addressName));
|
|
22
|
+
if (!data)
|
|
23
|
+
return undefined;
|
|
24
|
+
return SessionRecord.deserialize(data);
|
|
25
|
+
},
|
|
26
|
+
async storeSession(addressName, record) {
|
|
27
|
+
await adapter.set(sessionKey(addressName), record);
|
|
28
|
+
},
|
|
29
|
+
async getOurIdentity() {
|
|
30
|
+
const data = await adapter.get(OUR_IDENTITY);
|
|
31
|
+
if (!data)
|
|
32
|
+
throw new Error('Our identity not found in storage');
|
|
33
|
+
return { pubKey: fromBase64(data.pubKey), privKey: fromBase64(data.privKey) };
|
|
34
|
+
},
|
|
35
|
+
async loadPreKey(preKeyId) {
|
|
36
|
+
const data = await adapter.get(preKeyKey(preKeyId));
|
|
37
|
+
if (!data)
|
|
38
|
+
return undefined;
|
|
39
|
+
return { pubKey: fromBase64(data.pubKey), privKey: fromBase64(data.privKey) };
|
|
40
|
+
},
|
|
41
|
+
async loadSignedPreKey(signedPreKeyId) {
|
|
42
|
+
const data = await adapter.get(signedPreKeyKey(signedPreKeyId));
|
|
43
|
+
if (!data)
|
|
44
|
+
return undefined;
|
|
45
|
+
return { pubKey: fromBase64(data.pubKey), privKey: fromBase64(data.privKey) };
|
|
46
|
+
},
|
|
47
|
+
async removePreKey(preKeyId) {
|
|
48
|
+
await adapter.delete(preKeyKey(preKeyId));
|
|
49
|
+
},
|
|
50
|
+
async getOurRegistrationId() {
|
|
51
|
+
const v = await adapter.get(REG_ID);
|
|
52
|
+
if (typeof v !== 'number')
|
|
53
|
+
throw new Error('registration id missing');
|
|
54
|
+
return v;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { StorageAdapter, BatchOp } from './types';
|
|
2
|
+
export declare class InMemoryStorage implements StorageAdapter {
|
|
3
|
+
private store;
|
|
4
|
+
name: string;
|
|
5
|
+
constructor(initial?: Record<string, any>);
|
|
6
|
+
get<T = any>(key: string): Promise<T | undefined>;
|
|
7
|
+
set<T = any>(key: string, value: T): Promise<void>;
|
|
8
|
+
delete(key: string): Promise<void>;
|
|
9
|
+
batch(ops: BatchOp[]): Promise<void>;
|
|
10
|
+
clear(): Promise<void>;
|
|
11
|
+
close(): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
export declare function createInMemoryStorage(initial?: Record<string, any>): InMemoryStorage;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export class InMemoryStorage {
|
|
2
|
+
store = new Map();
|
|
3
|
+
name = 'in-memory';
|
|
4
|
+
constructor(initial) {
|
|
5
|
+
if (initial)
|
|
6
|
+
Object.entries(initial).forEach(([k, v]) => this.store.set(k, v));
|
|
7
|
+
}
|
|
8
|
+
async get(key) {
|
|
9
|
+
return this.store.has(key) ? this.store.get(key) : undefined;
|
|
10
|
+
}
|
|
11
|
+
async set(key, value) {
|
|
12
|
+
this.store.set(key, value);
|
|
13
|
+
}
|
|
14
|
+
async delete(key) {
|
|
15
|
+
this.store.delete(key);
|
|
16
|
+
}
|
|
17
|
+
async batch(ops) {
|
|
18
|
+
for (const op of ops) {
|
|
19
|
+
if (op.type === 'put')
|
|
20
|
+
this.store.set(op.key, op.value);
|
|
21
|
+
else
|
|
22
|
+
this.store.delete(op.key);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async clear() {
|
|
26
|
+
this.store.clear();
|
|
27
|
+
}
|
|
28
|
+
async close() {
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function createInMemoryStorage(initial) {
|
|
32
|
+
return new InMemoryStorage(initial);
|
|
33
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export * from './types';
|
|
2
|
+
export * from './in-memory';
|
|
3
|
+
export * from './migrations';
|
|
4
|
+
export * from './adapter';
|
|
5
|
+
import { StorageAdapter } from './types';
|
|
6
|
+
export declare class StorageManager {
|
|
7
|
+
adapter: StorageAdapter;
|
|
8
|
+
constructor(adapter: StorageAdapter);
|
|
9
|
+
get<T = any>(key: string): Promise<T | undefined>;
|
|
10
|
+
set<T = any>(key: string, value: T): Promise<void>;
|
|
11
|
+
delete(key: string): Promise<void>;
|
|
12
|
+
batch(ops: Array<{
|
|
13
|
+
type: 'put' | 'del';
|
|
14
|
+
key: string;
|
|
15
|
+
value?: any;
|
|
16
|
+
}>): Promise<void>;
|
|
17
|
+
close(): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
export declare function createStorageManager(adapter: StorageAdapter): StorageManager;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export * from './types';
|
|
2
|
+
export * from './in-memory';
|
|
3
|
+
export * from './migrations';
|
|
4
|
+
export * from './adapter';
|
|
5
|
+
export class StorageManager {
|
|
6
|
+
adapter;
|
|
7
|
+
constructor(adapter) {
|
|
8
|
+
this.adapter = adapter;
|
|
9
|
+
}
|
|
10
|
+
get(key) {
|
|
11
|
+
return this.adapter.get(key);
|
|
12
|
+
}
|
|
13
|
+
set(key, value) {
|
|
14
|
+
return this.adapter.set(key, value);
|
|
15
|
+
}
|
|
16
|
+
delete(key) {
|
|
17
|
+
return this.adapter.delete(key);
|
|
18
|
+
}
|
|
19
|
+
batch(ops) {
|
|
20
|
+
return this.adapter.batch(ops);
|
|
21
|
+
}
|
|
22
|
+
async close() {
|
|
23
|
+
if (this.adapter.close)
|
|
24
|
+
return this.adapter.close();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function createStorageManager(adapter) {
|
|
28
|
+
return new StorageManager(adapter);
|
|
29
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type BatchOp = {
|
|
2
|
+
type: 'put' | 'del';
|
|
3
|
+
key: string;
|
|
4
|
+
value?: any;
|
|
5
|
+
};
|
|
6
|
+
export interface StorageAdapter {
|
|
7
|
+
get<T = any>(key: string): Promise<T | undefined>;
|
|
8
|
+
set<T = any>(key: string, value: T): Promise<void>;
|
|
9
|
+
delete(key: string): Promise<void>;
|
|
10
|
+
batch(ops: BatchOp[]): Promise<void>;
|
|
11
|
+
clear?(): Promise<void>;
|
|
12
|
+
close?(): Promise<void>;
|
|
13
|
+
name?: string;
|
|
14
|
+
migrate?(fromVersion: number, toVersion: number): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
export interface StorageManagerOptions {
|
|
17
|
+
adapter: StorageAdapter;
|
|
18
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export interface IdentityKeyPair {
|
|
2
|
+
readonly publicKey: Uint8Array // 32 bytes Ed25519
|
|
3
|
+
readonly privateKey: Uint8Array // 64 bytes Ed25519
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface DHKeyPair {
|
|
7
|
+
readonly publicKey: Uint8Array // 32 bytes X25519
|
|
8
|
+
readonly privateKey: Uint8Array // 32 bytes X25519
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface SignalAsymmetricAPI {
|
|
12
|
+
/* Identity (Ed25519) */
|
|
13
|
+
generateIdentityKeyPair(): Promise<IdentityKeyPair>
|
|
14
|
+
sign(
|
|
15
|
+
privateKey: Uint8Array,
|
|
16
|
+
message: Uint8Array
|
|
17
|
+
): Uint8Array
|
|
18
|
+
verify(
|
|
19
|
+
publicKey: Uint8Array,
|
|
20
|
+
message: Uint8Array,
|
|
21
|
+
signature: Uint8Array
|
|
22
|
+
): boolean
|
|
23
|
+
|
|
24
|
+
/* DH (X25519) */
|
|
25
|
+
generateDHKeyPair(): Promise<DHKeyPair>
|
|
26
|
+
calculateAgreement(
|
|
27
|
+
publicKey: Uint8Array,
|
|
28
|
+
privateKey: Uint8Array
|
|
29
|
+
): Uint8Array
|
|
30
|
+
|
|
31
|
+
/* Conversion */
|
|
32
|
+
convertIdentityPublicToX25519(
|
|
33
|
+
edPublicKey: Uint8Array
|
|
34
|
+
): Uint8Array
|
|
35
|
+
|
|
36
|
+
convertIdentityPrivateToX25519(
|
|
37
|
+
edPrivateKey: Uint8Array
|
|
38
|
+
): Uint8Array
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const signalCrypto: SignalAsymmetricAPI
|