core-3nweb-client-lib 0.49.6 → 0.49.8
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/build/api-defs/asmail.d.ts +1 -0
- package/build/api-defs/files.d.ts +11 -5
- package/build/core/asmail/delivery/common.d.ts +8 -28
- package/build/core/asmail/delivery/common.js +1 -47
- package/build/core/asmail/delivery/index.js +3 -3
- package/build/core/asmail/delivery/per-recipient-wip.js +3 -2
- package/build/core/asmail/inbox/index.d.ts +8 -32
- package/build/core/asmail/inbox/index.js +120 -170
- package/build/core/asmail/inbox/msg-indexing/dataset/v0/sql-db-v-none.d.ts +11 -0
- package/build/{lib-client/xsp-fs/util/for-arrays.js → core/asmail/inbox/msg-indexing/dataset/v0/sql-db-v-none.js} +7 -13
- package/build/core/asmail/inbox/msg-indexing/dataset/v1/logs.d.ts +33 -0
- package/build/core/asmail/inbox/msg-indexing/dataset/v1/logs.js +72 -0
- package/build/core/asmail/inbox/msg-indexing/dataset/v1/sql-db.d.ts +28 -0
- package/build/core/asmail/inbox/msg-indexing/dataset/v1/sql-db.js +477 -0
- package/build/core/asmail/inbox/msg-indexing/index.d.ts +12 -26
- package/build/core/asmail/inbox/msg-indexing/index.js +47 -68
- package/build/core/asmail/inbox/msg-on-disk.d.ts +0 -5
- package/build/core/asmail/inbox/msg-on-disk.js +2 -24
- package/build/core/asmail/index.js +8 -7
- package/build/core/asmail/msg/common.d.ts +0 -1
- package/build/core/asmail/msg/common.js +1 -1
- package/build/core/index.js +7 -4
- package/build/core/keyring/common.d.ts +1 -0
- package/build/core/keyring/common.js +2 -1
- package/build/core/keyring/index.d.ts +7 -44
- package/build/core/keyring/index.js +88 -222
- package/build/core/keyring/introductory-keys/published-intro-key.d.ts +17 -0
- package/build/core/keyring/introductory-keys/published-intro-key.js +190 -0
- package/build/core/keyring/peer-keys/dataset/v1/json-files.d.ts +105 -0
- package/build/core/keyring/peer-keys/dataset/v1/json-files.js +89 -0
- package/build/core/keyring/peer-keys/dataset/v2/logs.d.ts +43 -0
- package/build/core/keyring/peer-keys/dataset/v2/logs.js +116 -0
- package/build/core/keyring/peer-keys/dataset/v2/peer-keys-db.d.ts +73 -0
- package/build/core/keyring/peer-keys/dataset/v2/peer-keys-db.js +543 -0
- package/build/core/keyring/peer-keys/index.d.ts +24 -0
- package/build/core/keyring/peer-keys/index.js +208 -0
- package/build/core/storage/synced/obj-files.js +10 -3
- package/build/core/storage/synced/obj-status.d.ts +1 -1
- package/build/core/storage/synced/obj-status.js +8 -2
- package/build/core/storage/synced/storage.d.ts +4 -4
- package/build/core/storage/system-folders/apps-data.d.ts +0 -1
- package/build/core/storage/system-folders/apps-data.js +2 -6
- package/build/lib-client/asmail/recipient.d.ts +1 -0
- package/build/lib-client/asmail/recipient.js +11 -0
- package/build/lib-client/fs-utils/fs-sync-utils.d.ts +1 -1
- package/build/lib-client/fs-utils/fs-sync-utils.js +70 -13
- package/build/lib-client/xsp-fs/folder-node.js +13 -3
- package/build/lib-client/xsp-fs/fs.js +3 -1
- package/build/lib-client/xsp-fs/node-in-fs.d.ts +1 -0
- package/build/lib-client/xsp-fs/node-in-fs.js +7 -2
- package/build/lib-client/xsp-fs/node-persistence.js +1 -3
- package/build/lib-client/xsp-fs/xsp-payload-v2.js +1 -2
- package/build/lib-common/dataset-sync/per-device-json-array-logs.d.ts +58 -0
- package/build/lib-common/dataset-sync/per-device-json-array-logs.js +432 -0
- package/build/lib-common/dataset-sync/single-file.d.ts +10 -0
- package/build/lib-common/dataset-sync/single-file.js +77 -0
- package/build/lib-common/number-line.d.ts +10 -0
- package/build/lib-common/number-line.js +66 -0
- package/build/lib-common/objs-on-disk/obj-version-file.d.ts +1 -1
- package/build/lib-common/objs-on-disk/obj-version-file.js +3 -3
- package/build/lib-common/rng-def.d.ts +1 -0
- package/build/lib-common/rng-def.js +8 -0
- package/build/lib-sqlite-on-3nstorage/for-sqlite.d.ts +38 -0
- package/build/lib-sqlite-on-3nstorage/for-sqlite.js +162 -0
- package/build/lib-sqlite-on-3nstorage/index.d.ts +18 -5
- package/build/lib-sqlite-on-3nstorage/index.js +49 -26
- package/build/tests/caps-api/file-sink-checks/splice.js +72 -1
- package/build/tests/caps-api/fs-checks/sync-on-one-dev/upload.js +27 -1
- package/build/tests/caps-api/fs-checks/sync-with-two-devs/update-propagation.js +12 -3
- package/build/tests/computer.3nweb.core/inbox/msg-indexing.js +16 -12
- package/build/tests/computer.3nweb.core/keyrings.js +113 -28
- package/build/tests/jasmine.js +2 -2
- package/build/tests/units/number-line.js +9 -9
- package/package.json +2 -1
- package/build/core/asmail/inbox/msg-indexing/logs-n-entries.d.ts +0 -52
- package/build/core/asmail/inbox/msg-indexing/logs-n-entries.js +0 -333
- package/build/core/asmail/inbox/msg-indexing/sql-indexing.d.ts +0 -4
- package/build/core/asmail/inbox/msg-indexing/sql-indexing.js +0 -319
- package/build/core/keyring/correspondent-keys.d.ts +0 -149
- package/build/core/keyring/correspondent-keys.js +0 -403
- package/build/core/keyring/id-to-email-map.d.ts +0 -38
- package/build/core/keyring/id-to-email-map.js +0 -105
- package/build/core/keyring/keyring-storage.d.ts +0 -9
- package/build/core/keyring/keyring-storage.js +0 -54
- package/build/core/keyring/published-intro-key.d.ts +0 -45
- package/build/core/keyring/published-intro-key.js +0 -200
- package/build/lib-client/xsp-fs/util/for-arrays.d.ts +0 -1
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Copyright (C) 2026 3NSoft Inc.
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify it under
|
|
6
|
+
the terms of the GNU General Public License as published by the Free Software
|
|
7
|
+
Foundation, either version 3 of the License, or (at your option) any later
|
|
8
|
+
version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful, but
|
|
11
|
+
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
13
|
+
See the GNU General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU General Public License along with
|
|
16
|
+
this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.makePeerKeysDB = makePeerKeysDB;
|
|
20
|
+
const canonical_address_1 = require("../../../../../lib-common/canonical-address");
|
|
21
|
+
const for_sqlite_1 = require("../../../../../lib-sqlite-on-3nstorage/for-sqlite");
|
|
22
|
+
const lib_sqlite_on_3nstorage_1 = require("../../../../../lib-sqlite-on-3nstorage");
|
|
23
|
+
const json_files_1 = require("../v1/json-files");
|
|
24
|
+
const common_1 = require("../../../common");
|
|
25
|
+
const rng_def_1 = require("../../../../../lib-common/rng-def");
|
|
26
|
+
const buffer_utils_1 = require("../../../../../lib-common/buffer-utils");
|
|
27
|
+
const ecma_nacl_1 = require("ecma-nacl");
|
|
28
|
+
const number_line_1 = require("../../../../../lib-common/number-line");
|
|
29
|
+
const logs_1 = require("./logs");
|
|
30
|
+
const queryToCreatePeerKeysDbV2 = [
|
|
31
|
+
// XXX although we put foreign key into create clauses, current library binary is ignoring it even with pragma on
|
|
32
|
+
// yet, we follow connection accordingly
|
|
33
|
+
// `--sql
|
|
34
|
+
// PRAGMA foreign_keys = ON
|
|
35
|
+
// `,
|
|
36
|
+
`--sql
|
|
37
|
+
CREATE TABLE peers (
|
|
38
|
+
peerCAddr TEXT NOT NULL PRIMARY KEY,
|
|
39
|
+
peerAddr TEXT NOT NULL
|
|
40
|
+
)
|
|
41
|
+
`,
|
|
42
|
+
// sending_key_pairs contains material for sending messages.
|
|
43
|
+
// Sending pair is suggested by peer.
|
|
44
|
+
`--sql
|
|
45
|
+
CREATE TABLE sending_key_pairs (
|
|
46
|
+
peerCAddr TEXT NOT NULL PRIMARY KEY REFERENCES peers ON DELETE CASCADE,
|
|
47
|
+
--
|
|
48
|
+
pairAlg TEXT NOT NULL,
|
|
49
|
+
pairTS INTEGER NOT NULL,
|
|
50
|
+
--
|
|
51
|
+
sentMsgsCount INTEGER,
|
|
52
|
+
sentMsgLastTS INTEGER,
|
|
53
|
+
--
|
|
54
|
+
peerPKey BLOB NOT NULL,
|
|
55
|
+
peerKId TEXT NOT NULL,
|
|
56
|
+
--
|
|
57
|
+
pids TEXT NOT NULL,
|
|
58
|
+
--
|
|
59
|
+
senderSKey BLOB NOT NULL,
|
|
60
|
+
senderPKey TEXT NOT NULL,
|
|
61
|
+
senderKId TEXT NOT NULL,
|
|
62
|
+
--
|
|
63
|
+
msgMasterKeyAlg TEXT NOT NULL,
|
|
64
|
+
msgMasterKey BLOB NOT NULL
|
|
65
|
+
)
|
|
66
|
+
`,
|
|
67
|
+
// reception_key_pairs are used for reading incoming messages.
|
|
68
|
+
// Reception pairs are suggested by this side to respective peers, ratcheting/updating keys of both sides.
|
|
69
|
+
// When peer encrypts message to a new pair, it implies that pair is ratcheted to be in use, making current
|
|
70
|
+
// pair old, and allowing to suggest next new pair, advancing key ratcheting.
|
|
71
|
+
// In a scenario when this side starts messaging, it suggests pairs with peer's introductory key, known at the
|
|
72
|
+
// time. Introductory key may change in time, resulting in different suggested pairs before peer ever replies.
|
|
73
|
+
// These pairs should be kept till peer replies, implying which pair is used, allowing to drop other ones.
|
|
74
|
+
`--sql
|
|
75
|
+
CREATE TABLE reception_key_pairs (
|
|
76
|
+
peerCAddr TEXT NOT NULL REFERENCES peers ON DELETE CASCADE,
|
|
77
|
+
ratchetStage TEXT NOT NULL,
|
|
78
|
+
pids TEXT NOT NULL,
|
|
79
|
+
--
|
|
80
|
+
pairAlg TEXT NOT NULL,
|
|
81
|
+
pairTS INTEGER NOT NULL,
|
|
82
|
+
--
|
|
83
|
+
receivedMsgsCounts TEXT,
|
|
84
|
+
receivedMsgLastTS INTEGER,
|
|
85
|
+
--
|
|
86
|
+
peerPKey BLOB NOT NULL,
|
|
87
|
+
peerKId TEXT NOT NULL,
|
|
88
|
+
isPeerIntroKey INTEGER NOT NULL,
|
|
89
|
+
--
|
|
90
|
+
recipientSKey BLOB NOT NULL,
|
|
91
|
+
recipientPKey TEXT NOT NULL,
|
|
92
|
+
recipientKId TEXT NOT NULL,
|
|
93
|
+
--
|
|
94
|
+
msgMasterKeyAlg TEXT NOT NULL,
|
|
95
|
+
msgMasterKey BLOB NOT NULL,
|
|
96
|
+
--
|
|
97
|
+
PRIMARY KEY (peerCAddr, recipientKId, peerKId),
|
|
98
|
+
UNIQUE (peerCAddr, recipientKId) ON CONFLICT FAIL
|
|
99
|
+
)
|
|
100
|
+
`,
|
|
101
|
+
`--sql
|
|
102
|
+
CREATE INDEX reception_pair_stage ON reception_key_pairs (peerCAddr, ratchetStage)
|
|
103
|
+
`,
|
|
104
|
+
`--sql
|
|
105
|
+
CREATE TABLE reception_key_pairs_pids (
|
|
106
|
+
peerCAddr TEXT NOT NULL,
|
|
107
|
+
peerKId TEXT NOT NULL,
|
|
108
|
+
recipientKId TEXT NOT NULL,
|
|
109
|
+
pid TEXT NOT NULL,
|
|
110
|
+
PRIMARY KEY (peerCAddr, recipientKId, peerKId, pid),
|
|
111
|
+
FOREIGN KEY (peerCAddr, recipientKId, peerKId) REFERENCES reception_key_pairs ON DELETE CASCADE
|
|
112
|
+
)
|
|
113
|
+
`,
|
|
114
|
+
`--sql
|
|
115
|
+
CREATE INDEX pid_to_reception_pair ON reception_key_pairs_pids (pid)
|
|
116
|
+
`
|
|
117
|
+
].join(';\n');
|
|
118
|
+
const sendingKeyPairsTabFields = {
|
|
119
|
+
peerCAddr: 'as-is',
|
|
120
|
+
pairAlg: 'as-is',
|
|
121
|
+
pairTS: 'as-is',
|
|
122
|
+
sentMsgsCount: 'as-is',
|
|
123
|
+
sentMsgLastTS: 'as-is',
|
|
124
|
+
peerPKey: 'as-is',
|
|
125
|
+
peerKId: 'as-is',
|
|
126
|
+
pids: for_sqlite_1.jsonTransform,
|
|
127
|
+
senderSKey: 'as-is',
|
|
128
|
+
senderPKey: 'as-is',
|
|
129
|
+
senderKId: 'as-is',
|
|
130
|
+
msgMasterKey: 'as-is',
|
|
131
|
+
msgMasterKeyAlg: 'as-is'
|
|
132
|
+
};
|
|
133
|
+
const receptionKeyPairsTabFields = {
|
|
134
|
+
peerCAddr: 'as-is',
|
|
135
|
+
ratchetStage: 'as-is',
|
|
136
|
+
pids: for_sqlite_1.jsonTransform,
|
|
137
|
+
pairTS: 'as-is',
|
|
138
|
+
pairAlg: 'as-is',
|
|
139
|
+
receivedMsgsCounts: for_sqlite_1.optJsonTransform,
|
|
140
|
+
receivedMsgLastTS: 'as-is',
|
|
141
|
+
peerPKey: 'as-is',
|
|
142
|
+
peerKId: 'as-is',
|
|
143
|
+
isPeerIntroKey: for_sqlite_1.booleanTransform,
|
|
144
|
+
recipientSKey: 'as-is',
|
|
145
|
+
recipientPKey: 'as-is',
|
|
146
|
+
recipientKId: 'as-is',
|
|
147
|
+
msgMasterKey: 'as-is',
|
|
148
|
+
msgMasterKeyAlg: 'as-is'
|
|
149
|
+
};
|
|
150
|
+
const pidsIndexTabFields = {
|
|
151
|
+
peerCAddr: 'as-is',
|
|
152
|
+
peerKId: 'as-is',
|
|
153
|
+
recipientKId: 'as-is',
|
|
154
|
+
pid: 'as-is'
|
|
155
|
+
};
|
|
156
|
+
const PEER_KEYS_DB = `peer-keys.sqlite`;
|
|
157
|
+
const PEER_KEYS_DB_ATTR = `msg-index-db`;
|
|
158
|
+
async function setMsgIndexInfoIntoAttrOf(dbFile, info) {
|
|
159
|
+
const completeInfo = {
|
|
160
|
+
dbVersion: 1,
|
|
161
|
+
...info
|
|
162
|
+
};
|
|
163
|
+
await dbFile.updateXAttrs({
|
|
164
|
+
set: { [PEER_KEYS_DB_ATTR]: completeInfo }
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
async function readOrInitializeDB(dbsFS) {
|
|
168
|
+
if (await dbsFS.checkFilePresence(PEER_KEYS_DB)) {
|
|
169
|
+
const dbFile = await dbsFS.writableFile(PEER_KEYS_DB, { create: false });
|
|
170
|
+
return await lib_sqlite_on_3nstorage_1.SQLiteOn3NStorage.makeSynced(dbFile);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
const dbFile = await dbsFS.writableFile(PEER_KEYS_DB, { create: true, exclusive: true });
|
|
174
|
+
const peerKeys = await lib_sqlite_on_3nstorage_1.SQLiteOn3NStorage.makeSynced(dbFile);
|
|
175
|
+
peerKeys.db.run(queryToCreatePeerKeysDbV2);
|
|
176
|
+
await setMsgIndexInfoIntoAttrOf(peerKeys.dbFile, {});
|
|
177
|
+
await peerKeys.saveToFile();
|
|
178
|
+
await peerKeys.dbFileSync.upload();
|
|
179
|
+
await dbsFS.v.sync.upload('');
|
|
180
|
+
return peerKeys;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
async function generatePids(random) {
|
|
184
|
+
const pids = [];
|
|
185
|
+
for (let i = 0; i < 5; i += 1) {
|
|
186
|
+
pids[i] = await (0, rng_def_1.stringOfB64UrlSafeChars)(common_1.PID_LENGTH, random);
|
|
187
|
+
if (i > 0) {
|
|
188
|
+
if (pids.slice(0, i).includes(pids[i])) {
|
|
189
|
+
i -= 1;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return pids;
|
|
194
|
+
}
|
|
195
|
+
async function makePeerKeysDB(peerKeysFS, random) {
|
|
196
|
+
const keys = await readOrInitializeDB(peerKeysFS);
|
|
197
|
+
let needToSave = false;
|
|
198
|
+
function noteDbMotificationForFileSaving() {
|
|
199
|
+
if (!needToSave && (keys.db.getRowsModified() > 0)) {
|
|
200
|
+
needToSave = true;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
async function saveIfNeeded() {
|
|
204
|
+
if (needToSave) {
|
|
205
|
+
await keys.saveToFile();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function addPeer(peerAddr) {
|
|
209
|
+
const peerCAddr = (0, canonical_address_1.toCanonicalAddress)(peerAddr);
|
|
210
|
+
keys.db.exec(`--sql
|
|
211
|
+
INSERT INTO peers (peerCAddr, peerAddr) VALUES ($peerCAddr, $peerAddr) ON CONFLICT DO NOTHING`, { $peerCAddr: peerCAddr, $peerAddr: peerAddr });
|
|
212
|
+
noteDbMotificationForFileSaving();
|
|
213
|
+
}
|
|
214
|
+
function getSendingPair(peerCAddr) {
|
|
215
|
+
const pair = (0, for_sqlite_1.selectFromTable)(keys.db, 'sending_key_pairs', '*', { peerCAddr }, sendingKeyPairsTabFields);
|
|
216
|
+
return (pair ? pair[0] : undefined);
|
|
217
|
+
}
|
|
218
|
+
function addSendingPair(pair) {
|
|
219
|
+
(0, for_sqlite_1.performTableInsert)(keys.db, 'sending_key_pairs', pair, sendingKeyPairsTabFields);
|
|
220
|
+
noteDbMotificationForFileSaving();
|
|
221
|
+
}
|
|
222
|
+
function updateSendingPair(pair) {
|
|
223
|
+
(0, for_sqlite_1.updateInTable)(keys.db, 'sending_key_pairs', (0, for_sqlite_1.passOmitting)(pair, 'peerCAddr'), { peerCAddr: pair.peerCAddr }, sendingKeyPairsTabFields);
|
|
224
|
+
noteDbMotificationForFileSaving();
|
|
225
|
+
}
|
|
226
|
+
function updateSentMsgCountInSendingPair(peerCAddr, sentMsgsCount) {
|
|
227
|
+
updateSendingPair({ peerCAddr, sentMsgLastTS: Date.now(), sentMsgsCount });
|
|
228
|
+
}
|
|
229
|
+
function updateSendingPairFromSuggested(peerCAddr, pair, cryptoMaterial) {
|
|
230
|
+
updateSendingPair({
|
|
231
|
+
...cryptoMaterial,
|
|
232
|
+
peerCAddr,
|
|
233
|
+
pids: pair.pids,
|
|
234
|
+
pairTS: pair.timestamp,
|
|
235
|
+
peerKId: pair.recipientPKey.kid,
|
|
236
|
+
sentMsgsCount: 0,
|
|
237
|
+
sentMsgLastTS: 0
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
function addSendingPairFromSuggested(peerCAddr, pair, cryptoMaterial) {
|
|
241
|
+
addSendingPair({
|
|
242
|
+
...cryptoMaterial,
|
|
243
|
+
peerCAddr,
|
|
244
|
+
pids: pair.pids,
|
|
245
|
+
pairTS: pair.timestamp,
|
|
246
|
+
peerKId: pair.recipientPKey.kid,
|
|
247
|
+
sentMsgsCount: 0,
|
|
248
|
+
sentMsgLastTS: 0
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
function findKeyToMatchSuggestedForSendingPair(peerCAddr, thisSideKid) {
|
|
252
|
+
const pairs = (0, for_sqlite_1.selectFromTable)(keys.db, 'reception_key_pairs', ['ratchetStage', 'recipientSKey', 'recipientPKey', 'pairAlg'], { peerCAddr, recipientKId: thisSideKid }, receptionKeyPairsTabFields);
|
|
253
|
+
if (pairs) {
|
|
254
|
+
return {
|
|
255
|
+
pkey: pairs[0].recipientPKey,
|
|
256
|
+
skey: pairs[0].recipientSKey,
|
|
257
|
+
pairAlg: pairs[0].pairAlg
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
function getPeerAddressForCanonical(peerCAddr) {
|
|
262
|
+
const res = keys.db.exec(`--sql
|
|
263
|
+
SELECT peerAddr FROM peers WHERE peerCAddr=$peerCAddr`, { '$peerCAddr': peerCAddr });
|
|
264
|
+
return ((res.length > 0) ? (0, lib_sqlite_on_3nstorage_1.objectFromQueryExecResult)(res[0])[0].peerAddr : undefined);
|
|
265
|
+
}
|
|
266
|
+
function addReceptionPair(pair) {
|
|
267
|
+
(0, for_sqlite_1.performTableInsert)(keys.db, 'reception_key_pairs', pair, receptionKeyPairsTabFields);
|
|
268
|
+
const { peerCAddr, peerKId, pids, recipientKId } = pair;
|
|
269
|
+
for (const pid of pids) {
|
|
270
|
+
(0, for_sqlite_1.performTableInsert)(keys.db, 'reception_key_pairs_pids', { peerCAddr, peerKId, pid, recipientKId }, pidsIndexTabFields);
|
|
271
|
+
}
|
|
272
|
+
noteDbMotificationForFileSaving();
|
|
273
|
+
}
|
|
274
|
+
function markPairAsInUse(peerCAddr, peerKId, recipientKId) {
|
|
275
|
+
var _a;
|
|
276
|
+
const suggested = getPairSuggestedToPeer(peerCAddr);
|
|
277
|
+
if (((suggested === null || suggested === void 0 ? void 0 : suggested.senderKid) !== peerKId) || ((suggested === null || suggested === void 0 ? void 0 : suggested.recipientPKey.kid) !== recipientKId)) {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
(0, for_sqlite_1.updateInTable)(keys.db, 'reception_key_pairs', { ratchetStage: 'old' }, { peerCAddr, ratchetStage: 'in_use' }, receptionKeyPairsTabFields);
|
|
281
|
+
(0, for_sqlite_1.updateInTable)(keys.db, 'reception_key_pairs', { ratchetStage: 'in_use' }, { peerCAddr, peerKId, recipientKId }, receptionKeyPairsTabFields);
|
|
282
|
+
const veryOld = (_a = (0, for_sqlite_1.selectFromTable)(keys.db, 'reception_key_pairs', ['peerKId', 'recipientKId', 'pairTS'], { peerCAddr, ratchetStage: 'old' }, receptionKeyPairsTabFields)) === null || _a === void 0 ? void 0 : _a.sort((a, b) => (b.pairTS - a.pairTS)).slice(1);
|
|
283
|
+
if (veryOld && (veryOld.length > 1)) {
|
|
284
|
+
for (const { recipientKId, peerKId } of veryOld) {
|
|
285
|
+
const whereParams = (0, for_sqlite_1.queryParamsFrom)({ peerCAddr, recipientKId, peerKId }, receptionKeyPairsTabFields);
|
|
286
|
+
const whereClause = (0, for_sqlite_1.andEqualExprFor)(whereParams);
|
|
287
|
+
keys.db.exec(`--sql
|
|
288
|
+
DELETE FROM reception_key_pairs WHERE ${whereClause}`, whereParams);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
noteDbMotificationForFileSaving();
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
function updateReceivedMsgCountIn(peerCAddr, peerKId, recipientKId, msgCount, msgTS) {
|
|
295
|
+
const pair = (0, for_sqlite_1.selectFromTable)(keys.db, 'reception_key_pairs', ['receivedMsgLastTS', 'receivedMsgsCounts'], { peerCAddr, peerKId, recipientKId }, receptionKeyPairsTabFields);
|
|
296
|
+
if (!pair) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
let { receivedMsgLastTS, receivedMsgsCounts } = pair[0];
|
|
300
|
+
if (!(0, number_line_1.addToNumberLineSegments)(receivedMsgsCounts, msgCount)) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
(0, for_sqlite_1.updateInTable)(keys.db, 'reception_key_pairs', { receivedMsgsCounts, receivedMsgLastTS: Math.max(receivedMsgLastTS, msgTS) }, { peerCAddr, peerKId, recipientKId }, receptionKeyPairsTabFields);
|
|
304
|
+
noteDbMotificationForFileSaving();
|
|
305
|
+
}
|
|
306
|
+
function findReceptionPairsForPId(pid) {
|
|
307
|
+
const keyIds = (0, for_sqlite_1.selectFromTable)(keys.db, 'reception_key_pairs_pids', ['peerCAddr', 'peerKId', 'recipientKId'], { pid }, pidsIndexTabFields);
|
|
308
|
+
if (!keyIds) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
const foundPairs = [];
|
|
312
|
+
for (const { peerCAddr, peerKId, recipientKId } of keyIds) {
|
|
313
|
+
const key = (0, for_sqlite_1.selectFromTable)(keys.db, 'reception_key_pairs', ['msgMasterKey', 'ratchetStage', 'pairAlg'], { peerCAddr, peerKId, recipientKId }, receptionKeyPairsTabFields);
|
|
314
|
+
const { msgMasterKey, ratchetStage, pairAlg } = key[0];
|
|
315
|
+
foundPairs.push({
|
|
316
|
+
msgMasterKey: msgMasterKey, pairAlg: pairAlg, peerCAddr: peerCAddr,
|
|
317
|
+
peerKId: peerKId, ratchetStage: ratchetStage, recipientKId: recipientKId
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
return ((foundPairs.length > 0) ? foundPairs : undefined);
|
|
321
|
+
}
|
|
322
|
+
async function absorbV1DataIfPresent(keyringFS) {
|
|
323
|
+
var _a;
|
|
324
|
+
const v1Entries = await (0, json_files_1.checkIfV1AndReadPeerKeys)(keyringFS);
|
|
325
|
+
if (v1Entries)
|
|
326
|
+
for (const peer of v1Entries) {
|
|
327
|
+
const peerCAddr = (0, canonical_address_1.toCanonicalAddress)(peer.correspondent);
|
|
328
|
+
addPeer(peer.correspondent);
|
|
329
|
+
if (peer.receptionPairs.suggested) {
|
|
330
|
+
addReceptionPair((0, json_files_1.v1ReceptionPairToV2RecipientKeyPairDbEntry)(peer.receptionPairs.suggested, 'suggested', peerCAddr));
|
|
331
|
+
}
|
|
332
|
+
if (peer.receptionPairs.inUse) {
|
|
333
|
+
addReceptionPair((0, json_files_1.v1ReceptionPairToV2RecipientKeyPairDbEntry)(peer.receptionPairs.inUse, 'in_use', peerCAddr));
|
|
334
|
+
}
|
|
335
|
+
if (peer.receptionPairs.old) {
|
|
336
|
+
addReceptionPair((0, json_files_1.v1ReceptionPairToV2RecipientKeyPairDbEntry)(peer.receptionPairs.old, 'old', peerCAddr));
|
|
337
|
+
}
|
|
338
|
+
if (((_a = peer.sendingPair) === null || _a === void 0 ? void 0 : _a.type) === 'ratcheted') {
|
|
339
|
+
addSendingPair((0, json_files_1.v1RatchetedSendingPairToV2SendingKeyPairDbEntry)(peer.sendingPair, peerCAddr));
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
await keys.saveToFile();
|
|
343
|
+
await keys.dbFileSync.upload();
|
|
344
|
+
}
|
|
345
|
+
function getPairSuggestedToPeer(peerCAddr) {
|
|
346
|
+
const pairs = (0, for_sqlite_1.selectFromTable)(keys.db, 'reception_key_pairs', ['pids', 'isPeerIntroKey', 'peerKId', 'pairTS', 'recipientKId', 'recipientPKey'], { peerCAddr, ratchetStage: 'suggested' }, receptionKeyPairsTabFields);
|
|
347
|
+
if (!pairs) {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
const { pids, peerKId, pairTS, recipientKId, recipientPKey } = pairs[0];
|
|
351
|
+
return {
|
|
352
|
+
pids: pids,
|
|
353
|
+
recipientPKey: {
|
|
354
|
+
kid: recipientKId,
|
|
355
|
+
k: recipientPKey
|
|
356
|
+
},
|
|
357
|
+
timestamp: pairTS,
|
|
358
|
+
senderKid: peerKId
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
async function generateSuggestedPairOnPeerIntroKey(peerCAddr, peerIntroPKey) {
|
|
362
|
+
const { kid: peerKId, k: peerPKey } = peerIntroPKey;
|
|
363
|
+
return await generateAndAddSuggestedPair(peerCAddr, peerKId, buffer_utils_1.base64.open(peerPKey), false);
|
|
364
|
+
}
|
|
365
|
+
async function generateAndAddSuggestedPair(peerCAddr, peerKId, peerPKey, isPeerIntroKey) {
|
|
366
|
+
const recipientKey = await (0, common_1.generateKeyPair)(random);
|
|
367
|
+
const recipientSKey = buffer_utils_1.base64.open(recipientKey.skey.k);
|
|
368
|
+
const msgMasterKey = ecma_nacl_1.box.calc_dhshared_key(peerPKey, recipientSKey);
|
|
369
|
+
const pids = await generatePids(random);
|
|
370
|
+
const pairTS = Date.now();
|
|
371
|
+
const dbEntry = {
|
|
372
|
+
peerCAddr,
|
|
373
|
+
ratchetStage: 'suggested',
|
|
374
|
+
pids,
|
|
375
|
+
pairTS,
|
|
376
|
+
pairAlg: recipientKey.skey.alg,
|
|
377
|
+
isPeerIntroKey,
|
|
378
|
+
peerKId,
|
|
379
|
+
peerPKey,
|
|
380
|
+
recipientKId: recipientKey.pkey.kid,
|
|
381
|
+
recipientPKey: recipientKey.pkey.k,
|
|
382
|
+
recipientSKey,
|
|
383
|
+
msgMasterKey,
|
|
384
|
+
msgMasterKeyAlg: ecma_nacl_1.secret_box.JWK_ALG_NAME,
|
|
385
|
+
receivedMsgLastTS: null,
|
|
386
|
+
receivedMsgsCounts: []
|
|
387
|
+
};
|
|
388
|
+
addReceptionPair(dbEntry);
|
|
389
|
+
noteDbMotificationForFileSaving();
|
|
390
|
+
return {
|
|
391
|
+
nextCrypto: {
|
|
392
|
+
pids,
|
|
393
|
+
recipientPKey: {
|
|
394
|
+
kid: recipientKey.pkey.kid,
|
|
395
|
+
k: recipientKey.pkey.k
|
|
396
|
+
},
|
|
397
|
+
senderKid: peerKId,
|
|
398
|
+
timestamp: pairTS
|
|
399
|
+
},
|
|
400
|
+
entryToLog: dbEntry
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
async function generateRegularSuggestedPairIfNeeded(peerCAddr) {
|
|
404
|
+
const pairsInUse = (0, for_sqlite_1.selectFromTable)(keys.db, 'reception_key_pairs', ['pairTS'], { peerCAddr, ratchetStage: 'in_use' }, receptionKeyPairsTabFields);
|
|
405
|
+
if (pairsInUse && ((pairsInUse[0].pairTS + common_1.MIN_PERIOD_FOR_PAIR) > Date.now())) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
const sendingPair = getSendingPair(peerCAddr);
|
|
409
|
+
if (!sendingPair) {
|
|
410
|
+
throw new Error(`Regular sending pair should be present to use peer's key in next suggested crypto`);
|
|
411
|
+
}
|
|
412
|
+
const { peerKId, peerPKey } = sendingPair;
|
|
413
|
+
return await generateAndAddSuggestedPair(peerCAddr, peerKId, peerPKey, false);
|
|
414
|
+
}
|
|
415
|
+
function getPeerKeysInfo(peerAddr) {
|
|
416
|
+
var _a;
|
|
417
|
+
const peerCAddr = (0, canonical_address_1.toCanonicalAddress)(peerAddr);
|
|
418
|
+
const sp = getSendingPair(peerCAddr);
|
|
419
|
+
const rpSuggested = (0, for_sqlite_1.selectFromTable)(keys.db, 'reception_key_pairs', '*', { peerCAddr, ratchetStage: 'suggested' }, receptionKeyPairsTabFields);
|
|
420
|
+
const rpInUse = (0, for_sqlite_1.selectFromTable)(keys.db, 'reception_key_pairs', '*', { peerCAddr, ratchetStage: 'in_use' }, receptionKeyPairsTabFields);
|
|
421
|
+
const rpOld = (_a = (0, for_sqlite_1.selectFromTable)(keys.db, 'reception_key_pairs', '*', { peerCAddr, ratchetStage: 'old' }, receptionKeyPairsTabFields)) === null || _a === void 0 ? void 0 : _a.sort((a, b) => (b.pairTS - a.pairTS));
|
|
422
|
+
return {
|
|
423
|
+
sendingPair: (sp ? {
|
|
424
|
+
alg: sp.pairAlg,
|
|
425
|
+
pids: sp.pids,
|
|
426
|
+
type: 'ratcheted',
|
|
427
|
+
timestamp: sp.pairTS,
|
|
428
|
+
senderKId: sp.senderKId,
|
|
429
|
+
recipientKId: sp.peerKId,
|
|
430
|
+
sentMsgs: {
|
|
431
|
+
count: sp.sentMsgsCount,
|
|
432
|
+
lastTS: sp.sentMsgLastTS
|
|
433
|
+
}
|
|
434
|
+
} : null),
|
|
435
|
+
receptionPairs: {
|
|
436
|
+
suggested: (rpSuggested ? recipientKeyPairDbEntryIntoDisplayInfo(rpSuggested[0]) : null),
|
|
437
|
+
inUse: (rpInUse ? recipientKeyPairDbEntryIntoDisplayInfo(rpInUse[0]) : null),
|
|
438
|
+
old: (rpOld ? recipientKeyPairDbEntryIntoDisplayInfo(rpOld[0]) : null)
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
async function saveAndSync() {
|
|
443
|
+
await saveIfNeeded();
|
|
444
|
+
const syncedIndexDbVersion = await keys.dbFileSync.upload();
|
|
445
|
+
return syncedIndexDbVersion;
|
|
446
|
+
}
|
|
447
|
+
// XXX at a startup we should check if current local latest is too old
|
|
448
|
+
// XXX one should watch for reconnect event and run similar checks of possibly missed events
|
|
449
|
+
async function absorbOpsFromOtherDevices(changeProc, ops) {
|
|
450
|
+
await changeProc.startOrChain(async () => {
|
|
451
|
+
for (const op of ops) {
|
|
452
|
+
if (op.p) {
|
|
453
|
+
if (op.p.opType === 'pair-from-peer') {
|
|
454
|
+
const { peerAddr, sendingPairRecord: inJSON } = op.p;
|
|
455
|
+
const existingPair = getSendingPair(inJSON.peerCAddr);
|
|
456
|
+
if (existingPair) {
|
|
457
|
+
if (existingPair.pairTS < inJSON.pairTS) {
|
|
458
|
+
updateSendingPair((0, logs_1.sendingPairEntryFromJSON)(inJSON));
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
if (!getPeerAddressForCanonical(inJSON.peerCAddr)) {
|
|
463
|
+
addPeer(peerAddr);
|
|
464
|
+
}
|
|
465
|
+
addSendingPair((0, logs_1.sendingPairEntryFromJSON)(inJSON));
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
else if (op.p.opType === 'pair-suggested-to-peer') {
|
|
469
|
+
const { peerAddr, receivingPairRecord: inJSON } = op.p;
|
|
470
|
+
const existingPair = getPairSuggestedToPeer(inJSON.peerCAddr);
|
|
471
|
+
if (existingPair) {
|
|
472
|
+
if (existingPair.timestamp < inJSON.pairTS) {
|
|
473
|
+
// XXX replace existingPair with one from inJSON
|
|
474
|
+
// let's note that in broken situation we may suggest fixing moves
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
else {
|
|
478
|
+
if (!getPeerAddressForCanonical(inJSON.peerCAddr)) {
|
|
479
|
+
addPeer(peerAddr);
|
|
480
|
+
}
|
|
481
|
+
addReceptionPair((0, logs_1.recipientPairEntryFromJSON)(inJSON));
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
else if (op.p.opType === 'peer-started-using-pair') {
|
|
485
|
+
const { peerCAddr, peerKId, recipientKId } = op.p;
|
|
486
|
+
markPairAsInUse(peerCAddr, peerKId, recipientKId);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
else if (op.syncPoint) {
|
|
490
|
+
// XXX
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
await saveIfNeeded();
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
function watchAndApplyOpsFromOtherDevices(changeProc, resetSyncInterval) {
|
|
497
|
+
return keys.dbFile.watch({
|
|
498
|
+
next: fsEvent => changeProc.startOrChain(async () => {
|
|
499
|
+
if (fsEvent.type === 'remote-change') {
|
|
500
|
+
// XXX get remote, compare,add to current what is missing, vs remote
|
|
501
|
+
resetSyncInterval();
|
|
502
|
+
}
|
|
503
|
+
})
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
return {
|
|
507
|
+
saveIfNeeded,
|
|
508
|
+
generateSuggestedPairOnPeerIntroKey,
|
|
509
|
+
getPairSuggestedToPeer,
|
|
510
|
+
generateRegularSuggestedPairIfNeeded,
|
|
511
|
+
addPeer,
|
|
512
|
+
getSendingPair,
|
|
513
|
+
updateSentMsgCountInSendingPair,
|
|
514
|
+
addSendingPairFromSuggested,
|
|
515
|
+
updateSendingPairFromSuggested,
|
|
516
|
+
findKeyToMatchSuggestedForSendingPair,
|
|
517
|
+
addReceptionPair,
|
|
518
|
+
markPairAsInUse,
|
|
519
|
+
findReceptionPairsForPId,
|
|
520
|
+
absorbV1DataIfPresent,
|
|
521
|
+
getPeerAddressForCanonical,
|
|
522
|
+
updateReceivedMsgCountIn,
|
|
523
|
+
absorbOpsFromOtherDevices,
|
|
524
|
+
watchAndApplyOpsFromOtherDevices,
|
|
525
|
+
saveAndSync,
|
|
526
|
+
getPeerKeysInfo
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
function recipientKeyPairDbEntryIntoDisplayInfo(p) {
|
|
530
|
+
return {
|
|
531
|
+
alg: p.pairAlg,
|
|
532
|
+
pids: p.pids,
|
|
533
|
+
recipientKId: p.recipientKId,
|
|
534
|
+
senderKId: p.peerKId,
|
|
535
|
+
timestamp: p.pairTS,
|
|
536
|
+
isSenderIntroKey: p.isPeerIntroKey,
|
|
537
|
+
receivedMsgs: {
|
|
538
|
+
counts: p.receivedMsgsCounts,
|
|
539
|
+
lastTS: p.receivedMsgLastTS
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
Object.freeze(exports);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { JWKeyPair, ASMailKeyPair } from '../common';
|
|
2
|
+
import { SuggestedNextKeyPair } from '../../asmail/msg/opener';
|
|
3
|
+
import { AsyncRNG } from '../../../lib-common/rng-def';
|
|
4
|
+
import { Logger } from '../../../lib-client/logging/log-to-file';
|
|
5
|
+
type WritableFS = web3n.files.WritableFS;
|
|
6
|
+
type JsonKey = web3n.keys.JsonKey;
|
|
7
|
+
export declare function makePeersKeyring(keyringFS: WritableFS, localFS: WritableFS, random: AsyncRNG, logger: Logger): Promise<{
|
|
8
|
+
getPeerAddressForCanonical: (peerCAddr: string) => string | undefined;
|
|
9
|
+
needIntroKeyFor: (peerAddr: string) => boolean;
|
|
10
|
+
suggestPairForPeerIntroKey: (peerAddr: string, peerIntroPKey: JsonKey) => Promise<SuggestedNextKeyPair>;
|
|
11
|
+
getSendingCryptoWithinEstablishedPair: (peerCAddr: string) => Promise<{
|
|
12
|
+
currentPair: ASMailKeyPair;
|
|
13
|
+
msgMasterKey: Uint8Array;
|
|
14
|
+
msgCount: number;
|
|
15
|
+
nextMsgCrypto?: SuggestedNextKeyPair;
|
|
16
|
+
}>;
|
|
17
|
+
absorbNextPairSuggestedByPeer: (peerAddr: string, pair: SuggestedNextKeyPair, introKey?: JWKeyPair) => Promise<void>;
|
|
18
|
+
findEstablishedReceptionPairs: (pid: string) => Pick<import("./dataset/v2/peer-keys-db").RecipientKeyPairDbEntry, "peerCAddr" | "peerKId" | "recipientKId" | "msgMasterKey" | "ratchetStage" | "pairAlg">[] | undefined;
|
|
19
|
+
markPairAsInUse: (peerCAddr: string, peerKId: string, recipientKId: string) => Promise<void>;
|
|
20
|
+
updateReceivedMsgCountIn: (peerCAddr: string, peerKId: string, recipientKId: string, msgCount: number, msgTS: number) => void;
|
|
21
|
+
close: () => Promise<void>;
|
|
22
|
+
getPeerKeysInfo: (peerAddr: string) => web3n.keys.CorrespondentKeysInfo | undefined;
|
|
23
|
+
}>;
|
|
24
|
+
export {};
|