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.
Files changed (87) hide show
  1. package/build/api-defs/asmail.d.ts +1 -0
  2. package/build/api-defs/files.d.ts +11 -5
  3. package/build/core/asmail/delivery/common.d.ts +8 -28
  4. package/build/core/asmail/delivery/common.js +1 -47
  5. package/build/core/asmail/delivery/index.js +3 -3
  6. package/build/core/asmail/delivery/per-recipient-wip.js +3 -2
  7. package/build/core/asmail/inbox/index.d.ts +8 -32
  8. package/build/core/asmail/inbox/index.js +120 -170
  9. package/build/core/asmail/inbox/msg-indexing/dataset/v0/sql-db-v-none.d.ts +11 -0
  10. package/build/{lib-client/xsp-fs/util/for-arrays.js → core/asmail/inbox/msg-indexing/dataset/v0/sql-db-v-none.js} +7 -13
  11. package/build/core/asmail/inbox/msg-indexing/dataset/v1/logs.d.ts +33 -0
  12. package/build/core/asmail/inbox/msg-indexing/dataset/v1/logs.js +72 -0
  13. package/build/core/asmail/inbox/msg-indexing/dataset/v1/sql-db.d.ts +28 -0
  14. package/build/core/asmail/inbox/msg-indexing/dataset/v1/sql-db.js +477 -0
  15. package/build/core/asmail/inbox/msg-indexing/index.d.ts +12 -26
  16. package/build/core/asmail/inbox/msg-indexing/index.js +47 -68
  17. package/build/core/asmail/inbox/msg-on-disk.d.ts +0 -5
  18. package/build/core/asmail/inbox/msg-on-disk.js +2 -24
  19. package/build/core/asmail/index.js +8 -7
  20. package/build/core/asmail/msg/common.d.ts +0 -1
  21. package/build/core/asmail/msg/common.js +1 -1
  22. package/build/core/index.js +7 -4
  23. package/build/core/keyring/common.d.ts +1 -0
  24. package/build/core/keyring/common.js +2 -1
  25. package/build/core/keyring/index.d.ts +7 -44
  26. package/build/core/keyring/index.js +88 -222
  27. package/build/core/keyring/introductory-keys/published-intro-key.d.ts +17 -0
  28. package/build/core/keyring/introductory-keys/published-intro-key.js +190 -0
  29. package/build/core/keyring/peer-keys/dataset/v1/json-files.d.ts +105 -0
  30. package/build/core/keyring/peer-keys/dataset/v1/json-files.js +89 -0
  31. package/build/core/keyring/peer-keys/dataset/v2/logs.d.ts +43 -0
  32. package/build/core/keyring/peer-keys/dataset/v2/logs.js +116 -0
  33. package/build/core/keyring/peer-keys/dataset/v2/peer-keys-db.d.ts +73 -0
  34. package/build/core/keyring/peer-keys/dataset/v2/peer-keys-db.js +543 -0
  35. package/build/core/keyring/peer-keys/index.d.ts +24 -0
  36. package/build/core/keyring/peer-keys/index.js +208 -0
  37. package/build/core/storage/synced/obj-files.js +10 -3
  38. package/build/core/storage/synced/obj-status.d.ts +1 -1
  39. package/build/core/storage/synced/obj-status.js +8 -2
  40. package/build/core/storage/synced/storage.d.ts +4 -4
  41. package/build/core/storage/system-folders/apps-data.d.ts +0 -1
  42. package/build/core/storage/system-folders/apps-data.js +2 -6
  43. package/build/lib-client/asmail/recipient.d.ts +1 -0
  44. package/build/lib-client/asmail/recipient.js +11 -0
  45. package/build/lib-client/fs-utils/fs-sync-utils.d.ts +1 -1
  46. package/build/lib-client/fs-utils/fs-sync-utils.js +70 -13
  47. package/build/lib-client/xsp-fs/folder-node.js +13 -3
  48. package/build/lib-client/xsp-fs/fs.js +3 -1
  49. package/build/lib-client/xsp-fs/node-in-fs.d.ts +1 -0
  50. package/build/lib-client/xsp-fs/node-in-fs.js +7 -2
  51. package/build/lib-client/xsp-fs/node-persistence.js +1 -3
  52. package/build/lib-client/xsp-fs/xsp-payload-v2.js +1 -2
  53. package/build/lib-common/dataset-sync/per-device-json-array-logs.d.ts +58 -0
  54. package/build/lib-common/dataset-sync/per-device-json-array-logs.js +432 -0
  55. package/build/lib-common/dataset-sync/single-file.d.ts +10 -0
  56. package/build/lib-common/dataset-sync/single-file.js +77 -0
  57. package/build/lib-common/number-line.d.ts +10 -0
  58. package/build/lib-common/number-line.js +66 -0
  59. package/build/lib-common/objs-on-disk/obj-version-file.d.ts +1 -1
  60. package/build/lib-common/objs-on-disk/obj-version-file.js +3 -3
  61. package/build/lib-common/rng-def.d.ts +1 -0
  62. package/build/lib-common/rng-def.js +8 -0
  63. package/build/lib-sqlite-on-3nstorage/for-sqlite.d.ts +38 -0
  64. package/build/lib-sqlite-on-3nstorage/for-sqlite.js +162 -0
  65. package/build/lib-sqlite-on-3nstorage/index.d.ts +18 -5
  66. package/build/lib-sqlite-on-3nstorage/index.js +49 -26
  67. package/build/tests/caps-api/file-sink-checks/splice.js +72 -1
  68. package/build/tests/caps-api/fs-checks/sync-on-one-dev/upload.js +27 -1
  69. package/build/tests/caps-api/fs-checks/sync-with-two-devs/update-propagation.js +12 -3
  70. package/build/tests/computer.3nweb.core/inbox/msg-indexing.js +16 -12
  71. package/build/tests/computer.3nweb.core/keyrings.js +113 -28
  72. package/build/tests/jasmine.js +2 -2
  73. package/build/tests/units/number-line.js +9 -9
  74. package/package.json +2 -1
  75. package/build/core/asmail/inbox/msg-indexing/logs-n-entries.d.ts +0 -52
  76. package/build/core/asmail/inbox/msg-indexing/logs-n-entries.js +0 -333
  77. package/build/core/asmail/inbox/msg-indexing/sql-indexing.d.ts +0 -4
  78. package/build/core/asmail/inbox/msg-indexing/sql-indexing.js +0 -319
  79. package/build/core/keyring/correspondent-keys.d.ts +0 -149
  80. package/build/core/keyring/correspondent-keys.js +0 -403
  81. package/build/core/keyring/id-to-email-map.d.ts +0 -38
  82. package/build/core/keyring/id-to-email-map.js +0 -105
  83. package/build/core/keyring/keyring-storage.d.ts +0 -9
  84. package/build/core/keyring/keyring-storage.js +0 -54
  85. package/build/core/keyring/published-intro-key.d.ts +0 -45
  86. package/build/core/keyring/published-intro-key.js +0 -200
  87. package/build/lib-client/xsp-fs/util/for-arrays.d.ts +0 -1
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*
3
- Copyright (C) 2015 - 2018, 2022, 2025 3NSoft Inc.
3
+ Copyright (C) 2015 - 2018, 2022, 2025 - 2026 3NSoft Inc.
4
4
 
5
5
  This program is free software: you can redistribute it and/or modify it under
6
6
  the terms of the GNU General Public License as published by the Free Software
@@ -16,124 +16,53 @@
16
16
  this program. If not, see <http://www.gnu.org/licenses/>.
17
17
  */
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
- exports.Keyrings = exports.KEY_USE = void 0;
20
- const correspondent_keys_1 = require("./correspondent-keys");
21
- const id_to_email_map_1 = require("./id-to-email-map");
19
+ exports.KEY_USE = void 0;
20
+ exports.makeKeyrings = makeKeyrings;
21
+ const peer_keys_1 = require("./peer-keys");
22
22
  const common_1 = require("./common");
23
23
  const async_cryptor_wrap_1 = require("../../lib-common/async-cryptor-wrap");
24
24
  const xsp_files_1 = require("xsp-files");
25
- const buffer_utils_1 = require("../../lib-common/buffer-utils");
26
25
  const canonical_address_1 = require("../../lib-common/canonical-address");
27
- const common_2 = require("../asmail/delivery/common");
28
- const keyring_storage_1 = require("./keyring-storage");
29
26
  const cryptor_work_labels_1 = require("../../lib-client/cryptor-work-labels");
30
- const published_intro_key_1 = require("./published-intro-key");
31
- var common_3 = require("./common");
32
- Object.defineProperty(exports, "KEY_USE", { enumerable: true, get: function () { return common_3.KEY_USE; } });
33
- const FILE_FOR_INTRO_KEY_ON_SERVER = 'introductory-keys/published-on-server.json';
34
- // XXX Keyring is just a storage and crypto functionality around keys
35
- class Keyrings {
36
- constructor(cryptor, random, logger) {
37
- this.cryptor = cryptor;
38
- this.random = random;
39
- this.logger = logger;
40
- /**
41
- * This is a map from correspondents' canonical addresses to key objects.
42
- */
43
- this.corrKeys = new Map();
44
- this.pairIdToEmailMap = new id_to_email_map_1.IdToEmailMap();
45
- this.workLabel = cryptor_work_labels_1.cryptoWorkLabels.makeRandom('asmail');
46
- this.storage = undefined;
47
- this.publishedKeys = undefined;
48
- this.asKeyPairsStorage = {
49
- pairIdToEmailMap: this.pairIdToEmailMap,
50
- saveChanges: this.saveChanges.bind(this)
51
- };
52
- Object.seal(this);
53
- }
54
- addCorrespondent(address, serialForm) {
55
- const ck = (serialForm ?
56
- new correspondent_keys_1.CorrespondentKeys(this.asKeyPairsStorage, this.random, undefined, serialForm) :
57
- new correspondent_keys_1.CorrespondentKeys(this.asKeyPairsStorage, this.random, address));
58
- if (this.corrKeys.has(ck.correspondent)) {
59
- throw new Error(`Correspondent with address ${ck.correspondent} is already present.`);
60
- }
61
- this.corrKeys.set(ck.correspondent, ck);
62
- if (serialForm) {
63
- ck.mapAllKeysIntoRing();
64
- }
65
- return ck;
66
- }
67
- async init(fs, getSigner, pkeyOnServer) {
68
- this.publishedKeys = await published_intro_key_1.PublishedIntroKey.makeAndInit(await fs.writableFile(FILE_FOR_INTRO_KEY_ON_SERVER), getSigner, this.random, pkeyOnServer);
69
- this.storage = (0, keyring_storage_1.makeKeyringStorage)(fs);
70
- await this.storage.start();
71
- const serialForm = await this.storage.load();
72
- if (serialForm) {
73
- const json = JSON.parse(serialForm);
74
- // TODO check json's fields
75
- // init data
76
- json.corrKeys.forEach((info) => {
77
- this.addCorrespondent(undefined, info);
78
- });
79
- }
80
- else {
81
- // save initial file, as there was none initially
82
- this.saveChanges();
83
- }
84
- }
85
- forASMail() {
86
- return {
87
- close: this.close.bind(this),
88
- decrypt: this.decrypt.bind(this),
89
- generateKeysToSend: this.generateKeysToSend.bind(this),
90
- needIntroKeyFor: this.needIntroKeyFor.bind(this),
91
- nextCrypto: this.nextCrypto.bind(this),
92
- };
93
- }
94
- saveChanges() {
95
- // pack bytes that need to be encrypted and saved
96
- const dataToSave = {
97
- corrKeys: []
27
+ const published_intro_key_1 = require("./introductory-keys/published-intro-key");
28
+ const fs_sync_utils_1 = require("../../lib-client/fs-utils/fs-sync-utils");
29
+ const ecma_nacl_1 = require("ecma-nacl");
30
+ const buffer_utils_1 = require("../../lib-common/buffer-utils");
31
+ var common_2 = require("./common");
32
+ Object.defineProperty(exports, "KEY_USE", { enumerable: true, get: function () { return common_2.KEY_USE; } });
33
+ const INTRO_KEYS_FOLDER = 'introductory-keys';
34
+ async function makeKeyrings(cryptor, random, logger, fs, localFS, getSigner, pkeyOnServer) {
35
+ const workLabel = cryptor_work_labels_1.cryptoWorkLabels.makeRandom('asmail');
36
+ const introKeysFS = await (0, fs_sync_utils_1.getOrMakeSyncedDirOnInit)(fs, INTRO_KEYS_FOLDER);
37
+ const publishedKeys = await (0, published_intro_key_1.makeHolderOfPublishedIntroKey)(introKeysFS, getSigner, random, pkeyOnServer);
38
+ const peerKeys = await (0, peer_keys_1.makePeersKeyring)(fs, localFS, random, logger);
39
+ async function generateIntroKeysToSendMsg(address, introPKeyFromServer) {
40
+ if (introPKeyFromServer.alg !== ecma_nacl_1.box.JWK_ALG_NAME) {
41
+ // XXX make some standard runtime exception, for unknown alg
42
+ throw new Error(`Unknown alg in introductory key: ${introPKeyFromServer.alg}`);
43
+ }
44
+ const oneTime = await (0, common_1.generateKeyPair)(random);
45
+ const currentPair = {
46
+ recipientKid: introPKeyFromServer.kid,
47
+ senderPKey: oneTime.pkey
98
48
  };
99
- for (const corrKeys of this.corrKeys.values()) {
100
- dataToSave.corrKeys.push(corrKeys.serialForm());
101
- }
102
- // trigger saving utility
103
- this.storage.save(JSON.stringify(dataToSave));
104
- }
105
- needIntroKeyFor(address) {
106
- address = (0, canonical_address_1.toCanonicalAddress)(address);
107
- return !this.corrKeys.has(address);
49
+ const msgMasterKey = ecma_nacl_1.box.calc_dhshared_key(buffer_utils_1.base64.open(introPKeyFromServer.k), buffer_utils_1.base64.open(oneTime.skey.k));
50
+ const nextNonce = await random(xsp_files_1.NONCE_LENGTH);
51
+ const encryptor = (0, async_cryptor_wrap_1.makeEncryptor)(cryptor, workLabel, msgMasterKey, nextNonce);
52
+ msgMasterKey.fill(0);
53
+ const nextMsgCrypto = await peerKeys.suggestPairForPeerIntroKey(address, introPKeyFromServer);
54
+ return { encryptor, currentPair, msgCount: 1, nextMsgCrypto };
108
55
  }
109
- ;
110
- async generateKeysToSend(address, introPKeyFromServer) {
111
- address = (0, canonical_address_1.toCanonicalAddress)(address);
112
- let ck = this.corrKeys.get(address);
113
- if (!ck) {
114
- if (!introPKeyFromServer) {
115
- throw new Error(`There are no known keys for given address ${address} and a key from a mail server is not given either.`);
116
- }
117
- ck = this.addCorrespondent(address);
118
- }
119
- const { msgMasterKey, currentPair, msgCount } = await ck.getSendingPair(introPKeyFromServer);
56
+ async function getEstablishedKeysToSendMsg(address) {
57
+ const { msgMasterKey, currentPair, msgCount, nextMsgCrypto } = await peerKeys.getSendingCryptoWithinEstablishedPair((0, canonical_address_1.toCanonicalAddress)(address));
120
58
  // prepare message encryptor
121
- const nextNonce = await this.random(xsp_files_1.NONCE_LENGTH);
122
- const encryptor = (0, async_cryptor_wrap_1.makeEncryptor)(this.cryptor, this.workLabel, msgMasterKey, nextNonce);
59
+ const nextNonce = await random(xsp_files_1.NONCE_LENGTH);
60
+ const encryptor = (0, async_cryptor_wrap_1.makeEncryptor)(cryptor, workLabel, msgMasterKey, nextNonce);
123
61
  msgMasterKey.fill(0);
124
- return { encryptor, currentPair, msgCount };
62
+ return { encryptor, currentPair, msgCount, nextMsgCrypto };
125
63
  }
126
- async nextCrypto(address) {
127
- address = (0, canonical_address_1.toCanonicalAddress)(address);
128
- let ck = this.corrKeys.get(address);
129
- if (!ck) {
130
- throw new Error(`No correspondent keys found for ${address}`);
131
- }
132
- const suggestPair = await ck.suggestPair();
133
- return suggestPair;
134
- }
135
- async decryptMsgKeyWithIntroPair(recipientKid, senderPKey, getMainObjHeader) {
136
- const recipKey = this.publishedKeys.find(recipientKid);
64
+ async function decryptMsgKeyWithIntroPair(recipientKid, senderPKey, getMainObjHeader) {
65
+ const recipKey = publishedKeys.find(recipientKid);
137
66
  if (!recipKey) {
138
67
  return;
139
68
  }
@@ -142,16 +71,17 @@ class Keyrings {
142
71
  if (h.length < msgKeyPackLen) {
143
72
  return;
144
73
  }
145
- const masterDecr = (0, correspondent_keys_1.msgMasterDecryptor)(this.cryptor, recipKey.pair.skey, { kid: '', k: senderPKey });
74
+ const msgMasterKey = ecma_nacl_1.box.calc_dhshared_key(buffer_utils_1.base64.open(senderPKey), buffer_utils_1.base64.open(recipKey.pair.skey.k));
75
+ const masterDecr = (0, async_cryptor_wrap_1.makeDecryptor)(cryptor, workLabel, msgMasterKey);
146
76
  try {
147
77
  const mainObjFileKey = await masterDecr.open(h.subarray(0, msgKeyPackLen));
148
- const info = {
78
+ const decrInfo = {
149
79
  correspondent: undefined,
150
80
  keyStatus: recipKey.role,
151
81
  key: mainObjFileKey,
152
82
  msgKeyPackLen
153
83
  };
154
- return info;
84
+ return { decrInfo, introKey: recipKey.pair };
155
85
  }
156
86
  catch (err) {
157
87
  if (!err.failedCipherVerification) {
@@ -162,60 +92,35 @@ class Keyrings {
162
92
  masterDecr.destroy();
163
93
  }
164
94
  }
165
- findEstablishedReceptionPairs(pid) {
166
- const emails = this.pairIdToEmailMap.getEmails(pid);
167
- if (!emails) {
168
- return;
169
- }
170
- const decryptors = [];
171
- for (const email of emails) {
172
- const ck = this.corrKeys.get(email);
173
- if (!ck) {
174
- return;
175
- }
176
- const rp = ck.getReceivingPair(pid);
177
- if (!rp) {
178
- return;
179
- }
180
- decryptors.push({
181
- correspondent: email,
182
- role: rp.role,
183
- pair: rp.pair
184
- });
185
- }
186
- return decryptors;
187
- }
188
- async decryptMsgKeyWithEstablishedPair(pid, getMainObjHeader) {
189
- const pairs = this.findEstablishedReceptionPairs(pid);
95
+ async function decryptMsgKeyWithEstablishedPair(pid, getMainObjHeader) {
96
+ const pairs = peerKeys.findEstablishedReceptionPairs(pid);
190
97
  if (!pairs) {
191
98
  return;
192
99
  }
193
100
  // try to open main object's file key from a header
194
101
  const h = await getMainObjHeader();
195
- for (const { correspondent, pair, role } of pairs) {
196
- const masterKey = buffer_utils_1.base64.open(pair.msgMasterKey);
197
- const masterDecr = (0, async_cryptor_wrap_1.makeDecryptor)(this.cryptor, this.workLabel, masterKey);
198
- masterKey.fill(0);
102
+ for (const { msgMasterKey, pairAlg, peerCAddr, ratchetStage, peerKId, recipientKId } of pairs) {
103
+ const masterDecr = (0, async_cryptor_wrap_1.makeDecryptor)(cryptor, workLabel, msgMasterKey);
104
+ msgMasterKey.fill(0);
199
105
  try {
200
- const msgKeyPackLen = msgKeyPackLenForPair(pair);
106
+ const msgKeyPackLen = (0, common_1.msgKeyPackSizeFor)(pairAlg);
201
107
  if (h.length < msgKeyPackLen) {
202
108
  continue;
203
109
  }
204
110
  const mainObjFileKey = await masterDecr.open(h.subarray(0, msgKeyPackLen));
205
111
  const keyInfo = {
206
- correspondent: correspondent,
207
- keyStatus: role,
112
+ correspondent: peerKeys.getPeerAddressForCanonical(peerCAddr) || peerCAddr,
113
+ keyStatus: ratchetStage,
208
114
  key: mainObjFileKey,
209
115
  msgKeyPackLen
210
116
  };
211
117
  // set pair as in use
212
118
  if (keyInfo.keyStatus === 'suggested') {
213
- const corrKeys = this.corrKeys.get(keyInfo.correspondent);
214
- corrKeys.markPairAsInUse(pair);
119
+ await peerKeys.markPairAsInUse(peerCAddr, peerKId, recipientKId);
215
120
  }
216
121
  return {
217
122
  keyInfo,
218
- incrMsgCount: msgCount => this.updateReceivedMsgCountIn(pair, msgCount)
123
+ incrMsgCount: msgCount => peerKeys.updateReceivedMsgCountIn(peerCAddr, peerKId, recipientKId, msgCount, Date.now())
219
124
  };
220
125
  }
221
126
  catch (err) {
@@ -228,58 +133,13 @@ class Keyrings {
228
133
  }
229
134
  }
230
135
  }
231
- /**
232
- * This method updates message counts and a timestamp in a given reception
233
- * pair.
234
- * @param rp is a sending pair, in which changes should be done. Note this
235
- * must be a shared structure at this point, not a copy of a pair.
236
- * @param msgCount is a message count that should be added to the pair.
237
- */
238
- updateReceivedMsgCountIn(rp, msgCount) {
239
- const lastTS = Date.now();
240
- if (!rp.receivedMsgs) {
241
- rp.receivedMsgs = { counts: [], lastTS };
242
- }
243
- (0, common_2.addToNumberLineSegments)(rp.receivedMsgs.counts, msgCount);
244
- rp.receivedMsgs.lastTS = lastTS;
245
- this.saveChanges();
246
- }
247
- absorbSuggestedNextKeyPair(correspondent, pair) {
248
- let ck = this.corrKeys.get(correspondent);
249
- if (!ck) {
250
- ck = this.addCorrespondent(correspondent);
251
- }
252
- if (pair.isSenderIntroKey) {
253
- const usedIntro = this.publishedKeys.find(pair.senderKid);
254
- if (!usedIntro) {
255
- throw new Error(`Recently used published intro key is not found`);
256
- }
257
- ck.ratchetUpSendingPair(pair, usedIntro.pair);
258
- }
259
- else {
260
- ck.ratchetUpSendingPair(pair);
261
- }
262
- // if (ck) {
263
- // ck.ratchetUpSendingPair(pair);
264
- // } else {
265
- // if (!pair.isSenderIntroKey) {
266
- // throw new Error(`Expected addition of correspondent to be done, when new `);
267
- // }
268
- // const usedIntro = this.publishedKeys.find(pair.senderKid);
269
- // if (!usedIntro) {
270
- // throw new Error(`Recently used published intro key is not found`);
271
- // }
272
- // ck = this.addCorrespondent(correspondent);
273
- // ck.ratchetUpSendingPair(pair, usedIntro.pair);
274
- // }
275
- this.saveChanges();
276
- }
277
- async decrypt(msgMeta, getMainObjHeader, getOpenedMsg, checkMidKeyCerts) {
136
+ async function decrypt(msgMeta, getMainObjHeader, getOpenedMsg, checkMidKeyCerts) {
278
137
  let decrInfo;
138
+ let introKey = undefined;
279
139
  let incrMsgCount;
280
140
  let openedMsg;
281
141
  if (msgMeta.pid) {
282
- const r = await this.decryptMsgKeyWithEstablishedPair(msgMeta.pid, getMainObjHeader);
142
+ const r = await decryptMsgKeyWithEstablishedPair(msgMeta.pid, getMainObjHeader);
283
143
  if (!r) {
284
144
  return;
285
145
  }
@@ -288,10 +148,12 @@ class Keyrings {
288
148
  openedMsg = await getOpenedMsg(decrInfo.key, decrInfo.msgKeyPackLen);
289
149
  }
290
150
  else {
291
- decrInfo = await this.decryptMsgKeyWithIntroPair(msgMeta.recipientKid, msgMeta.senderPKey, getMainObjHeader);
292
- if (!decrInfo) {
151
+ const r = await decryptMsgKeyWithIntroPair(msgMeta.recipientKid, msgMeta.senderPKey, getMainObjHeader);
152
+ if (!r) {
293
153
  return;
294
154
  }
155
+ decrInfo = r.decrInfo;
156
+ introKey = r.introKey;
295
157
  openedMsg = await getOpenedMsg(decrInfo.key, decrInfo.msgKeyPackLen);
296
158
  const certs = openedMsg.introCryptoCerts;
297
159
  const { address, pkey } = await checkMidKeyCerts(certs);
@@ -310,46 +172,50 @@ class Keyrings {
310
172
  incrMsgCount(openedMsg.msgCount);
311
173
  }
312
174
  // absorb next crypto
313
- const pair = openedMsg.nextCrypto;
314
- if (pair) {
175
+ const suggestedPair = openedMsg.nextCrypto;
176
+ if (suggestedPair) {
315
177
  try {
316
- if (msgMeta.recipientKid) {
317
- if (!pair.isSenderIntroKey) {
318
- throw new Error(`Introductory message is not referencing used intro key in the next crypto`);
319
- }
320
- if (msgMeta.recipientKid !== pair.senderKid) {
178
+ if (introKey) {
179
+ if (introKey.pkey.kid !== suggestedPair.senderKid) {
321
180
  throw new Error(`Introductory message is referencing wrong key in the next crypto`);
322
181
  }
182
+ await peerKeys.absorbNextPairSuggestedByPeer(decrInfo.correspondent, suggestedPair, introKey);
183
+ }
184
+ else {
185
+ await peerKeys.absorbNextPairSuggestedByPeer(decrInfo.correspondent, suggestedPair);
323
186
  }
324
- this.absorbSuggestedNextKeyPair(decrInfo.correspondent, pair);
325
187
  }
326
188
  catch (err) {
327
- this.logger.logError(err, `Fail to absorb next suggested key for messaging`);
189
+ logger.logError(err, `Fail to absorb next suggested key for messaging`);
328
190
  }
329
191
  }
330
192
  return { decrInfo, openedMsg };
331
193
  }
332
194
  ;
333
- async close() {
334
- await this.publishedKeys.close();
335
- await this.storage.close();
195
+ async function close() {
196
+ await publishedKeys.close();
197
+ await peerKeys.close();
336
198
  }
337
- makeKeyringsCAP() {
199
+ function makeKeyringsCAP() {
338
200
  const w = {
339
- introKeyOnASMailServer: this.publishedKeys.makeIntroKeyCAP(),
340
- getCorrespondentKeys: async (addr) => {
341
- var _a;
342
- const cAddr = (0, canonical_address_1.toCanonicalAddress)(addr);
343
- return (_a = this.corrKeys.get(cAddr)) === null || _a === void 0 ? void 0 : _a.toInfo();
344
- }
201
+ introKeyOnASMailServer: publishedKeys.makeIntroKeyCAP(),
202
+ getCorrespondentKeys: async (peerAddr) => peerKeys.getPeerKeysInfo(peerAddr)
345
203
  };
346
204
  return Object.freeze(w);
347
205
  }
348
- }
349
- exports.Keyrings = Keyrings;
350
- Object.freeze(Keyrings.prototype);
351
- Object.freeze(Keyrings);
352
- function msgKeyPackLenForPair(p) {
353
- return (0, common_1.msgKeyPackSizeFor)(p.recipientKey.skey.alg);
206
+ function forASMail() {
207
+ return {
208
+ close,
209
+ decrypt,
210
+ generateIntroKeysToSendMsg,
211
+ getEstablishedKeysToSendMsg,
212
+ needIntroKeyFor: peerKeys.needIntroKeyFor
213
+ };
214
+ }
215
+ return {
216
+ close,
217
+ makeKeyringsCAP,
218
+ forASMail
219
+ };
354
220
  }
355
221
  Object.freeze(exports);
@@ -0,0 +1,17 @@
1
+ import { ParamOnServer } from '../../../lib-client/asmail/service-config';
2
+ import { AsyncRNG } from '../../../lib-common/rng-def';
3
+ import { GetSigner } from '../../id-manager';
4
+ import { JWKeyPair, MsgKeyRole } from '../common';
5
+ type WritableFS = web3n.files.WritableFS;
6
+ type IntroKeyCAP = web3n.keys.Keyrings['introKeyOnASMailServer'];
7
+ export declare function makeHolderOfPublishedIntroKey(introKeysFS: WritableFS, getSigner: GetSigner, random: AsyncRNG, pkeyOnServer: ParamOnServer<'init-pub-key'>): Promise<{
8
+ startExpiryCheckProcess: () => void;
9
+ close: () => Promise<void>;
10
+ makeIntroKeyCAP: () => IntroKeyCAP;
11
+ find: (kid: string) => {
12
+ role: MsgKeyRole;
13
+ pair: JWKeyPair;
14
+ replacedAt?: number;
15
+ } | undefined;
16
+ }>;
17
+ export {};
@@ -0,0 +1,190 @@
1
+ "use strict";
2
+ /*
3
+ Copyright (C) 2015 - 2018, 2025 - 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.makeHolderOfPublishedIntroKey = makeHolderOfPublishedIntroKey;
20
+ const single_file_1 = require("../../../lib-common/dataset-sync/single-file");
21
+ const jwkeys_1 = require("../../../lib-common/jwkeys");
22
+ const common_1 = require("../common");
23
+ const INTRO_KEY_VALIDITY = 31 * 24 * 60 * 60;
24
+ const UPDATE_BEFORE_EXPIRY = 7 * 24 * 60 * 60;
25
+ const MAX_NUM_OF_PREV = 3;
26
+ const INTRO_KEY_ON_SERVER_FILE = `published-on-server.json`;
27
+ async function makeHolderOfPublishedIntroKey(introKeysFS, getSigner, random, pkeyOnServer) {
28
+ var _a;
29
+ const keysFile = await (0, single_file_1.initSyncedFile)(introKeysFS, INTRO_KEY_ON_SERVER_FILE, newFile => newFile.writeJSON({ previous: [] }));
30
+ let keys;
31
+ async function setKeysFromFile() {
32
+ keys = await keysFile.readJSON();
33
+ }
34
+ await setKeysFromFile();
35
+ const { changeProc, stopFileWatching, triggerUpload } = (0, single_file_1.watchAndApplyChangesFromOtherDevices)(keysFile, () => changeProc.startOrChain(setKeysFromFile), doOnConflict);
36
+ if (keysFile.isNew) {
37
+ await update();
38
+ }
39
+ updateIfCurrentExpired();
40
+ let periodicExpiryCheck = setTimeout(updateIfCurrentExpired, UPDATE_BEFORE_EXPIRY * 1000 / 20);
41
+ (_a = periodicExpiryCheck.unref) === null || _a === void 0 ? void 0 : _a.call(periodicExpiryCheck);
42
+ async function makeNewIntroKey() {
43
+ const signer = await getSigner();
44
+ const pair = await (0, common_1.generateKeyPair)(random);
45
+ const certs = {
46
+ pkeyCert: signer.certifyPublicKey(pair.pkey, INTRO_KEY_VALIDITY),
47
+ userCert: signer.userCert,
48
+ provCert: signer.providerCert
49
+ };
50
+ pair.createdAt = Date.now();
51
+ return { pair, certs };
52
+ }
53
+ function update() {
54
+ return changeProc.startOrChain(async () => {
55
+ const { certs, pair: keyPair } = await makeNewIntroKey();
56
+ await pkeyOnServer.setOnServer(certs);
57
+ retireCurrent(keyPair.createdAt);
58
+ keys.current = { keyPair, certs };
59
+ await keysFile.writeJSON(keys);
60
+ triggerUpload();
61
+ return certs;
62
+ });
63
+ }
64
+ ;
65
+ function retireCurrent(retiredAt) {
66
+ if (!keys.current) {
67
+ return;
68
+ }
69
+ const current = keys.current;
70
+ current.keyPair.retiredAt = retiredAt;
71
+ keys.previous.push(current.keyPair);
72
+ if (keys.previous.length > MAX_NUM_OF_PREV) {
73
+ keys.previous.splice(0, keys.previous.length - MAX_NUM_OF_PREV);
74
+ }
75
+ keys.current = undefined;
76
+ }
77
+ function doOnConflict() {
78
+ return changeProc.startOrChain(async () => {
79
+ var _a, _b, _c, _d, _e, _f;
80
+ const { remote } = await keysFile.v.sync.status(false);
81
+ if (!remote) {
82
+ return;
83
+ }
84
+ const onRemote = (await keysFile.v.readJSON({ remoteVersion: remote.latest })).json;
85
+ if (((_a = onRemote.current) === null || _a === void 0 ? void 0 : _a.keyPair.pkey.kid) === ((_b = keys.current) === null || _b === void 0 ? void 0 : _b.keyPair.pkey.kid)) {
86
+ await keysFile.v.sync.adoptRemote();
87
+ return;
88
+ }
89
+ const certsOnServer = await pkeyOnServer.getFromServer();
90
+ const currentOnServer = (certsOnServer ? (0, jwkeys_1.getPubKey)(certsOnServer.pkeyCert) : undefined);
91
+ if ((currentOnServer === null || currentOnServer === void 0 ? void 0 : currentOnServer.kid) === ((_c = keys.current) === null || _c === void 0 ? void 0 : _c.keyPair.pkey.kid)) {
92
+ await keysFile.v.sync.upload({ uploadVersion: remote.latest + 1 });
93
+ }
94
+ else if ((currentOnServer === null || currentOnServer === void 0 ? void 0 : currentOnServer.kid) === ((_d = onRemote.current) === null || _d === void 0 ? void 0 : _d.keyPair.pkey.kid)) {
95
+ await keysFile.v.sync.adoptRemote({ remoteVersion: remote.latest });
96
+ }
97
+ else {
98
+ await pkeyOnServer.setOnServer((_f = (_e = keys.current) === null || _e === void 0 ? void 0 : _e.certs) !== null && _f !== void 0 ? _f : null);
99
+ await keysFile.v.sync.upload({ uploadVersion: remote.latest + 1 });
100
+ }
101
+ })
102
+ .catch((exc) => {
103
+ if (exc.type === 'connect') {
104
+ // XXX need to have watcher for connection to complete upload
105
+ // Should this whole try-to-connect method be generic to reuse it?
106
+ }
107
+ });
108
+ }
109
+ /**
110
+ * This looks for a published key with a given key id. If it is found, an
111
+ * object is returned with following fields:
112
+ * - pair is JWK key pair;
113
+ * - role of a found key pair;
114
+ * - replacedAt field is present for a previously published key pair,
115
+ * telling, in milliseconds, when this key was superseded a newer one.
116
+ * Undefined is returned, when a key is not found.
117
+ * @param kid
118
+ * @return if key is found, object with following fields is returned:
119
+ */
120
+ function find(kid) {
121
+ // check current key
122
+ if (keys.current
123
+ && (keys.current.keyPair.skey.kid === kid)) {
124
+ return {
125
+ role: 'published_intro',
126
+ pair: keys.current.keyPair
127
+ };
128
+ }
129
+ // check previous key
130
+ const pair = keys.previous.find(({ skey }) => (skey.kid === kid));
131
+ if (pair) {
132
+ return {
133
+ role: 'prev_published_intro',
134
+ pair,
135
+ replacedAt: pair.retiredAt
136
+ };
137
+ }
138
+ // if nothing found, explicitly return undefined
139
+ return;
140
+ }
141
+ function makeIntroKeyCAP() {
142
+ const w = {
143
+ getCurrent,
144
+ makeAndPublishNew: update,
145
+ remove: removeCurrent
146
+ };
147
+ return Object.freeze(w);
148
+ }
149
+ async function getCurrent() {
150
+ var _a;
151
+ const certs = (_a = keys.current) === null || _a === void 0 ? void 0 : _a.certs;
152
+ return (certs ? certs : null);
153
+ }
154
+ async function removeCurrent() {
155
+ if (!keys.current) {
156
+ return;
157
+ }
158
+ return changeProc.startOrChain(async () => {
159
+ await pkeyOnServer.setOnServer(null);
160
+ retireCurrent(Math.floor(Date.now() / 1000));
161
+ await keysFile.writeJSON(keys);
162
+ triggerUpload();
163
+ });
164
+ }
165
+ function updateIfCurrentExpired() {
166
+ if (!keys.current) {
167
+ return;
168
+ }
169
+ const { pkeyCert } = keys.current.certs;
170
+ const expiryInSeconds = (0, jwkeys_1.getKeyCert)(pkeyCert).expiresAt;
171
+ const now = Math.floor(Date.now() / 1000);
172
+ if (expiryInSeconds < (now + UPDATE_BEFORE_EXPIRY)) {
173
+ update();
174
+ }
175
+ }
176
+ async function close() {
177
+ stopFileWatching();
178
+ if (periodicExpiryCheck) {
179
+ clearTimeout(periodicExpiryCheck);
180
+ periodicExpiryCheck = undefined;
181
+ }
182
+ }
183
+ return {
184
+ startExpiryCheckProcess: updateIfCurrentExpired,
185
+ close,
186
+ makeIntroKeyCAP,
187
+ find
188
+ };
189
+ }
190
+ Object.freeze(exports);