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
|
@@ -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.
|
|
20
|
-
|
|
21
|
-
const
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
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
|
|
122
|
-
const encryptor = (0, async_cryptor_wrap_1.makeEncryptor)(
|
|
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
|
|
127
|
-
|
|
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
|
|
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
|
|
78
|
+
const decrInfo = {
|
|
149
79
|
correspondent: undefined,
|
|
150
80
|
keyStatus: recipKey.role,
|
|
151
81
|
key: mainObjFileKey,
|
|
152
82
|
msgKeyPackLen
|
|
153
83
|
};
|
|
154
|
-
return
|
|
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
|
-
|
|
166
|
-
const
|
|
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 {
|
|
196
|
-
const
|
|
197
|
-
|
|
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 =
|
|
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:
|
|
207
|
-
keyStatus:
|
|
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
|
-
|
|
214
|
-
corrKeys.markPairAsInUse(pair);
|
|
119
|
+
await peerKeys.markPairAsInUse(peerCAddr, peerKId, recipientKId);
|
|
215
120
|
}
|
|
216
121
|
return {
|
|
217
122
|
keyInfo,
|
|
218
|
-
incrMsgCount: 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
|
|
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
|
-
|
|
292
|
-
if (!
|
|
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
|
|
314
|
-
if (
|
|
175
|
+
const suggestedPair = openedMsg.nextCrypto;
|
|
176
|
+
if (suggestedPair) {
|
|
315
177
|
try {
|
|
316
|
-
if (
|
|
317
|
-
if (
|
|
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
|
-
|
|
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
|
|
335
|
-
await
|
|
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:
|
|
340
|
-
getCorrespondentKeys: async (
|
|
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
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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);
|