@agentunion/fastaun-browser 0.2.17 → 0.2.18
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/dist/auth.d.ts +3 -0
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +293 -211
- package/dist/auth.js.map +1 -1
- package/dist/client.d.ts +11 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +1080 -812
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -1
- package/dist/config.js.map +1 -1
- package/dist/discovery.d.ts +3 -0
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +15 -1
- package/dist/discovery.js.map +1 -1
- package/dist/e2ee-group.d.ts +4 -0
- package/dist/e2ee-group.d.ts.map +1 -1
- package/dist/e2ee-group.js +327 -201
- package/dist/e2ee-group.js.map +1 -1
- package/dist/e2ee.d.ts +4 -0
- package/dist/e2ee.d.ts.map +1 -1
- package/dist/e2ee.js +176 -117
- package/dist/e2ee.js.map +1 -1
- package/dist/events.d.ts +3 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +4 -1
- package/dist/events.js.map +1 -1
- package/dist/keystore/indexeddb.d.ts +3 -0
- package/dist/keystore/indexeddb.d.ts.map +1 -1
- package/dist/keystore/indexeddb.js +153 -97
- package/dist/keystore/indexeddb.js.map +1 -1
- package/dist/logger.d.ts +37 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +112 -0
- package/dist/logger.js.map +1 -0
- package/dist/namespaces/auth.d.ts +4 -0
- package/dist/namespaces/auth.d.ts.map +1 -1
- package/dist/namespaces/auth.js +214 -101
- package/dist/namespaces/auth.js.map +1 -1
- package/dist/namespaces/custody.d.ts +3 -0
- package/dist/namespaces/custody.d.ts.map +1 -1
- package/dist/namespaces/custody.js +147 -75
- package/dist/namespaces/custody.js.map +1 -1
- package/dist/namespaces/meta.d.ts +3 -0
- package/dist/namespaces/meta.d.ts.map +1 -1
- package/dist/namespaces/meta.js +94 -43
- package/dist/namespaces/meta.js.map +1 -1
- package/dist/secret-store/indexeddb-store.d.ts +3 -0
- package/dist/secret-store/indexeddb-store.d.ts.map +1 -1
- package/dist/secret-store/indexeddb-store.js +57 -29
- package/dist/secret-store/indexeddb-store.js.map +1 -1
- package/dist/transport.d.ts +3 -0
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +74 -4
- package/dist/transport.js.map +1 -1
- package/package.json +1 -1
package/dist/e2ee-group.js
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
import { E2EEError, E2EEGroupSecretMissingError, } from './errors.js';
|
|
4
4
|
import { uint8ToBase64, base64ToUint8, pemToArrayBuffer, toBufferSource } from './crypto.js';
|
|
5
5
|
import { SUITE, _concatBytes as concatBytes, _certificateSha256Fingerprint as certificateSha256Fingerprint, _ecdsaSignDer as ecdsaSignDer, _ecdsaVerifyDer as ecdsaVerifyDer, _hkdfDerive as hkdfDerive, _aesGcmEncrypt as aesGcmEncrypt, _aesGcmDecrypt as aesGcmDecrypt, _randomNonce as randomNonce, _uuidV4 as uuidV4, _importCertPublicKeyEcdsa as importCertPublicKeyEcdsa, _importPrivateKeyEcdsa as importPrivateKeyEcdsa, } from './e2ee.js';
|
|
6
|
+
const _noopLog = { error: () => { }, warn: () => { }, info: () => { }, debug: () => { } };
|
|
7
|
+
// 顶层函数共享的模块 logger(client 构造时通过 setModuleLogger 注入)
|
|
8
|
+
let _moduleLog = _noopLog;
|
|
9
|
+
export function setModuleLogger(log) { _moduleLog = log; }
|
|
6
10
|
import { isJsonObject, } from './types.js';
|
|
7
11
|
const _encoder = new TextEncoder();
|
|
8
12
|
const _decoder = new TextDecoder();
|
|
@@ -472,7 +476,7 @@ export async function decryptGroupMessage(message, groupSecrets, senderCertPem,
|
|
|
472
476
|
return null;
|
|
473
477
|
const groupSecret = groupSecrets.get(epoch);
|
|
474
478
|
if (!groupSecret) {
|
|
475
|
-
|
|
479
|
+
_moduleLog.warn('[aun_core.e2ee-group] group message decrypt failed: epoch key not found');
|
|
476
480
|
return null;
|
|
477
481
|
}
|
|
478
482
|
try {
|
|
@@ -488,18 +492,18 @@ export async function decryptGroupMessage(message, groupSecrets, senderCertPem,
|
|
|
488
492
|
aadFrom = (aad.from ?? '');
|
|
489
493
|
// 外层路由字段与 AAD 绑定校验
|
|
490
494
|
if (outerGroupId && groupId !== outerGroupId) {
|
|
491
|
-
|
|
495
|
+
_moduleLog.warn('[aun_core.e2ee-group] AAD group_id mismatches outer route');
|
|
492
496
|
return null;
|
|
493
497
|
}
|
|
494
498
|
if (aadFrom) {
|
|
495
499
|
const outerFrom = (message.from ?? '');
|
|
496
500
|
const outerSender = String(message.sender_aid ?? '');
|
|
497
501
|
if (outerFrom && outerFrom !== aadFrom) {
|
|
498
|
-
|
|
502
|
+
_moduleLog.warn('[aun_core.e2ee-group] AAD from mismatches outer from');
|
|
499
503
|
return null;
|
|
500
504
|
}
|
|
501
505
|
if (outerSender && outerSender !== aadFrom) {
|
|
502
|
-
|
|
506
|
+
_moduleLog.warn('[aun_core.e2ee-group] AAD sender_aid mismatches outer sender_aid');
|
|
503
507
|
return null;
|
|
504
508
|
}
|
|
505
509
|
}
|
|
@@ -509,7 +513,7 @@ export async function decryptGroupMessage(message, groupSecrets, senderCertPem,
|
|
|
509
513
|
messageId = (message.message_id ?? '');
|
|
510
514
|
}
|
|
511
515
|
if (!groupId || !messageId) {
|
|
512
|
-
|
|
516
|
+
_moduleLog.warn('[aun_core.e2ee-group] group message decrypt failed: missing groupId or messageId');
|
|
513
517
|
return null;
|
|
514
518
|
}
|
|
515
519
|
const msgKey = await deriveGroupMsgKey(groupSecret, groupId, messageId);
|
|
@@ -549,16 +553,16 @@ export async function decryptGroupMessage(message, groupSecrets, senderCertPem,
|
|
|
549
553
|
if (requireSignature) {
|
|
550
554
|
// 零信任模式:必须有签名且有证书可验证
|
|
551
555
|
if (!senderSigB64) {
|
|
552
|
-
|
|
556
|
+
_moduleLog.warn(`reject group msg without sender signature (require_signature=true): group=${groupId} from=${aadFrom}`);
|
|
553
557
|
return null;
|
|
554
558
|
}
|
|
555
559
|
if (!senderCertPem) {
|
|
556
|
-
|
|
560
|
+
_moduleLog.warn(`拒绝群消息:有签名但无发送方证书可验证(零信任模式禁止跳过验签): group=${groupId} from=${aadFrom}`);
|
|
557
561
|
return null;
|
|
558
562
|
}
|
|
559
563
|
const verified = await _verifySenderSigGroup(senderCertPem, senderSigB64, ciphertext, tag, aadBytes);
|
|
560
564
|
if (!verified) {
|
|
561
|
-
|
|
565
|
+
_moduleLog.warn(`group msg sender signature verify failed: group=${groupId} from=${aadFrom}`);
|
|
562
566
|
return null;
|
|
563
567
|
}
|
|
564
568
|
if (isJsonObject(result.e2ee))
|
|
@@ -567,12 +571,12 @@ export async function decryptGroupMessage(message, groupSecrets, senderCertPem,
|
|
|
567
571
|
else if (senderCertPem) {
|
|
568
572
|
// 非零信任模式但提供了证书:有证书时强制验签
|
|
569
573
|
if (!senderSigB64) {
|
|
570
|
-
|
|
574
|
+
_moduleLog.warn(`reject group msg without sender signature: group=${groupId} from=${aadFrom}`);
|
|
571
575
|
return null;
|
|
572
576
|
}
|
|
573
577
|
const verified = await _verifySenderSigGroup(senderCertPem, senderSigB64, ciphertext, tag, aadBytes);
|
|
574
578
|
if (!verified) {
|
|
575
|
-
|
|
579
|
+
_moduleLog.warn(`group msg sender signature verify failed: group=${groupId} from=${aadFrom}`);
|
|
576
580
|
return null;
|
|
577
581
|
}
|
|
578
582
|
if (isJsonObject(result.e2ee))
|
|
@@ -581,7 +585,7 @@ export async function decryptGroupMessage(message, groupSecrets, senderCertPem,
|
|
|
581
585
|
return result;
|
|
582
586
|
}
|
|
583
587
|
catch (exc) {
|
|
584
|
-
|
|
588
|
+
_moduleLog.warn('[aun_core.e2ee-group] group message decrypt exception:', exc instanceof Error ? (exc.stack || exc.message) : String(exc));
|
|
585
589
|
return null;
|
|
586
590
|
}
|
|
587
591
|
}
|
|
@@ -857,7 +861,7 @@ async function assessIncomingEpochChain(keystore, aid, groupId, epoch, commitmen
|
|
|
857
861
|
const rid = rotationId.trim();
|
|
858
862
|
const rotator = rotatorAid.trim();
|
|
859
863
|
if (rid && !chain) {
|
|
860
|
-
|
|
864
|
+
_moduleLog.warn(`[aun_core.e2ee-group] reject missing epoch_chain new rotation key source=${source} group=${groupId} epoch=${epoch} rotation=${rid}`);
|
|
861
865
|
return { ok: false };
|
|
862
866
|
}
|
|
863
867
|
const current = await loadGroupSecret(keystore, aid, groupId);
|
|
@@ -868,7 +872,7 @@ async function assessIncomingEpochChain(keystore, aid, groupId, epoch, commitmen
|
|
|
868
872
|
return { ok: true };
|
|
869
873
|
if (rid && chain && currentChain && currentChain !== chain) {
|
|
870
874
|
if (!(currentPendingRotationId && currentPendingRotationId !== rid)) {
|
|
871
|
-
|
|
875
|
+
_moduleLog.warn(`[aun_core.e2ee-group] reject same epoch forked chain source=${source} group=${groupId} epoch=${epoch} rotation=${rid}`);
|
|
872
876
|
return { ok: false };
|
|
873
877
|
}
|
|
874
878
|
}
|
|
@@ -881,17 +885,17 @@ async function assessIncomingEpochChain(keystore, aid, groupId, epoch, commitmen
|
|
|
881
885
|
return { ok: true, unverified: true, reason: 'missing_prev_chain' };
|
|
882
886
|
if (!rotator) {
|
|
883
887
|
if (rid) {
|
|
884
|
-
|
|
888
|
+
_moduleLog.warn(`[aun_core.e2ee-group] reject missing rotator_aid new rotation key source=${source} group=${groupId} epoch=${epoch} rotation=${rid}`);
|
|
885
889
|
return { ok: false };
|
|
886
890
|
}
|
|
887
891
|
return { ok: true, unverified: true, reason: 'missing_rotator_aid' };
|
|
888
892
|
}
|
|
889
893
|
if (!await verifyEpochChain(chain, prevChain, epoch, commitment, rotator)) {
|
|
890
894
|
if (rid) {
|
|
891
|
-
|
|
895
|
+
_moduleLog.warn(`[aun_core.e2ee-group] reject epoch_chain verify failed new rotation key source=${source} group=${groupId} epoch=${epoch} rotation=${rid}`);
|
|
892
896
|
return { ok: false };
|
|
893
897
|
}
|
|
894
|
-
|
|
898
|
+
_moduleLog.warn(`[aun_core.e2ee-group] epoch_chain verify failed, accept in compat mode and mark unverified source=${source} group=${groupId} epoch=${epoch}`);
|
|
895
899
|
return { ok: true, unverified: true, reason: 'chain_mismatch_legacy' };
|
|
896
900
|
}
|
|
897
901
|
if (!rid)
|
|
@@ -975,12 +979,12 @@ export async function handleKeyDistribution(message, keystore, aid, initiatorCer
|
|
|
975
979
|
const manifest = isJsonObject(payload.manifest) ? payload.manifest : undefined;
|
|
976
980
|
if (initiatorCertPem) {
|
|
977
981
|
if (!manifest) {
|
|
978
|
-
|
|
982
|
+
_moduleLog.warn(`rejectno manifest keydistribute: group=${groupId} epoch=${epoch}`);
|
|
979
983
|
return false;
|
|
980
984
|
}
|
|
981
985
|
const valid = await verifyMembershipManifest(manifest, initiatorCertPem);
|
|
982
986
|
if (!valid) {
|
|
983
|
-
|
|
987
|
+
_moduleLog.warn(`group key distribution manifest signature verifyfailed: group=${groupId} epoch=${epoch}`);
|
|
984
988
|
return false;
|
|
985
989
|
}
|
|
986
990
|
if (manifest.group_id !== groupId || manifest.epoch !== epoch)
|
|
@@ -1195,6 +1199,8 @@ export class GroupKeyRequestThrottle {
|
|
|
1195
1199
|
* 所有密码学操作均为异步。
|
|
1196
1200
|
*/
|
|
1197
1201
|
export class GroupE2EEManager {
|
|
1202
|
+
_log = _noopLog;
|
|
1203
|
+
setLogger(log) { this._log = log; }
|
|
1198
1204
|
_identityFn;
|
|
1199
1205
|
_keystoreRef;
|
|
1200
1206
|
_replayGuard;
|
|
@@ -1223,69 +1229,96 @@ export class GroupE2EEManager {
|
|
|
1223
1229
|
}
|
|
1224
1230
|
/** 创建首个 epoch。返回 {epoch, commitment, distributions: [{to, payload}]}。 */
|
|
1225
1231
|
async createEpoch(groupId, memberAids) {
|
|
1226
|
-
const
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
epoch,
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1232
|
+
const tStart = Date.now();
|
|
1233
|
+
this._log.debug(`createEpoch enter: group_id=${groupId} members=${memberAids.length}`);
|
|
1234
|
+
try {
|
|
1235
|
+
const aid = this._currentAid();
|
|
1236
|
+
const gs = generateGroupSecret();
|
|
1237
|
+
const epoch = 1;
|
|
1238
|
+
const commitment = await computeMembershipCommitment(memberAids, epoch, groupId, gs);
|
|
1239
|
+
const epochChain = await computeEpochChain(null, epoch, commitment, aid);
|
|
1240
|
+
await storeGroupSecret(this._keystoreRef, aid, groupId, epoch, gs, commitment, memberAids, epochChain);
|
|
1241
|
+
const manifest = await this._signManifest(buildMembershipManifest(groupId, epoch, null, memberAids, { initiatorAid: aid }));
|
|
1242
|
+
const distPayload = await buildKeyDistribution(groupId, epoch, gs, memberAids, aid, manifest, epochChain);
|
|
1243
|
+
this._log.debug(`createEpoch exit: elapsed=${Date.now() - tStart}ms group_id=${groupId} epoch=${epoch}`);
|
|
1244
|
+
return {
|
|
1245
|
+
epoch,
|
|
1246
|
+
commitment,
|
|
1247
|
+
distributions: memberAids.filter(m => m !== aid).map(m => ({ to: m, payload: distPayload })),
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
catch (err) {
|
|
1251
|
+
this._log.debug(`createEpoch exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
1252
|
+
throw err;
|
|
1253
|
+
}
|
|
1239
1254
|
}
|
|
1240
1255
|
/** 轮换 epoch(踢人/退出后调用) */
|
|
1241
1256
|
async rotateEpoch(groupId, memberAids) {
|
|
1242
|
-
const
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1257
|
+
const tStart = Date.now();
|
|
1258
|
+
this._log.debug(`rotateEpoch enter: group_id=${groupId} members=${memberAids.length}`);
|
|
1259
|
+
try {
|
|
1260
|
+
const aid = this._currentAid();
|
|
1261
|
+
const current = await loadGroupSecret(this._keystoreRef, aid, groupId);
|
|
1262
|
+
const prevEpoch = current ? current.epoch : null;
|
|
1263
|
+
const prevChain = current?.epoch_chain ?? null;
|
|
1264
|
+
const newEpoch = (prevEpoch ?? 0) + 1;
|
|
1265
|
+
const gs = generateGroupSecret();
|
|
1266
|
+
const commitment = await computeMembershipCommitment(memberAids, newEpoch, groupId, gs);
|
|
1267
|
+
const epochChain = await computeEpochChain(prevChain, newEpoch, commitment, aid);
|
|
1268
|
+
const stored = await storeGroupSecret(this._keystoreRef, aid, groupId, newEpoch, gs, commitment, memberAids, epochChain);
|
|
1269
|
+
if (!stored) {
|
|
1270
|
+
throw new Error(`group ${groupId} epoch ${newEpoch} secret already exists or is newer; abort distribution`);
|
|
1271
|
+
}
|
|
1272
|
+
const manifest = await this._signManifest(buildMembershipManifest(groupId, newEpoch, prevEpoch, memberAids, { initiatorAid: aid }));
|
|
1273
|
+
const distPayload = await buildKeyDistribution(groupId, newEpoch, gs, memberAids, aid, manifest, epochChain);
|
|
1274
|
+
this._log.debug(`rotateEpoch exit: elapsed=${Date.now() - tStart}ms group_id=${groupId} epoch=${newEpoch}`);
|
|
1275
|
+
return {
|
|
1276
|
+
epoch: newEpoch,
|
|
1277
|
+
commitment,
|
|
1278
|
+
distributions: memberAids.filter(m => m !== aid).map(m => ({ to: m, payload: distPayload })),
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
catch (err) {
|
|
1282
|
+
this._log.debug(`rotateEpoch exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
1283
|
+
throw err;
|
|
1253
1284
|
}
|
|
1254
|
-
const manifest = await this._signManifest(buildMembershipManifest(groupId, newEpoch, prevEpoch, memberAids, { initiatorAid: aid }));
|
|
1255
|
-
const distPayload = await buildKeyDistribution(groupId, newEpoch, gs, memberAids, aid, manifest, epochChain);
|
|
1256
|
-
return {
|
|
1257
|
-
epoch: newEpoch,
|
|
1258
|
-
commitment,
|
|
1259
|
-
distributions: memberAids.filter(m => m !== aid).map(m => ({ to: m, payload: distPayload })),
|
|
1260
|
-
};
|
|
1261
1285
|
}
|
|
1262
1286
|
/** 指定目标 epoch 号轮换(配合服务端 CAS 使用) */
|
|
1263
1287
|
async rotateEpochTo(groupId, targetEpoch, memberAids, opts) {
|
|
1264
|
-
const
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1288
|
+
const tStart = Date.now();
|
|
1289
|
+
this._log.debug(`rotateEpochTo enter: group_id=${groupId} target_epoch=${targetEpoch} members=${memberAids.length} rotation_id=${opts?.rotationId ?? ''}`);
|
|
1290
|
+
try {
|
|
1291
|
+
const aid = this._currentAid();
|
|
1292
|
+
const current = await loadGroupSecret(this._keystoreRef, aid, groupId, targetEpoch - 1)
|
|
1293
|
+
?? await loadGroupSecret(this._keystoreRef, aid, groupId);
|
|
1294
|
+
let prevChain = current?.epoch_chain ?? null;
|
|
1295
|
+
if (!prevChain && opts?.prevChainHint) {
|
|
1296
|
+
prevChain = opts.prevChainHint;
|
|
1297
|
+
}
|
|
1298
|
+
const gs = generateGroupSecret();
|
|
1299
|
+
const commitment = await computeMembershipCommitment(memberAids, targetEpoch, groupId, gs);
|
|
1300
|
+
const epochChain = await computeEpochChain(prevChain, targetEpoch, commitment, aid);
|
|
1301
|
+
const rotationId = opts?.rotationId ?? '';
|
|
1302
|
+
const stored = await storeGroupSecret(this._keystoreRef, aid, groupId, targetEpoch, gs, commitment, memberAids, epochChain, rotationId);
|
|
1303
|
+
if (!stored) {
|
|
1304
|
+
throw new Error(`group ${groupId} epoch ${targetEpoch} secret already exists or is newer; abort distribution`);
|
|
1305
|
+
}
|
|
1306
|
+
const manifest = await this._signManifest(buildMembershipManifest(groupId, targetEpoch, targetEpoch - 1, memberAids, { initiatorAid: aid }));
|
|
1307
|
+
const distPayload = await buildKeyDistribution(groupId, targetEpoch, gs, memberAids, aid, manifest, epochChain);
|
|
1308
|
+
if (rotationId) {
|
|
1309
|
+
distPayload.rotation_id = rotationId;
|
|
1310
|
+
}
|
|
1311
|
+
this._log.debug(`rotateEpochTo exit: elapsed=${Date.now() - tStart}ms group_id=${groupId} epoch=${targetEpoch}`);
|
|
1312
|
+
return {
|
|
1313
|
+
epoch: targetEpoch,
|
|
1314
|
+
commitment,
|
|
1315
|
+
distributions: memberAids.filter(m => m !== aid).map(m => ({ to: m, payload: distPayload })),
|
|
1316
|
+
};
|
|
1278
1317
|
}
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
distPayload.rotation_id = rotationId;
|
|
1318
|
+
catch (err) {
|
|
1319
|
+
this._log.debug(`rotateEpochTo exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
1320
|
+
throw err;
|
|
1283
1321
|
}
|
|
1284
|
-
return {
|
|
1285
|
-
epoch: targetEpoch,
|
|
1286
|
-
commitment,
|
|
1287
|
-
distributions: memberAids.filter(m => m !== aid).map(m => ({ to: m, payload: distPayload })),
|
|
1288
|
-
};
|
|
1289
1322
|
}
|
|
1290
1323
|
/** 手动存储 group_secret。返回 false 表示 epoch 降级被拒。 */
|
|
1291
1324
|
async storeSecret(groupId, epoch, groupSecretBytes, commitment, memberAids, epochChain) {
|
|
@@ -1306,46 +1339,66 @@ export class GroupE2EEManager {
|
|
|
1306
1339
|
// ── 加解密 ────────────────────────────────────────
|
|
1307
1340
|
/** 加密群消息(含发送方签名)。无密钥时抛 E2EEGroupSecretMissingError。 */
|
|
1308
1341
|
async encrypt(groupId, payload, opts) {
|
|
1309
|
-
const
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1342
|
+
const tStart = Date.now();
|
|
1343
|
+
this._log.debug(`encrypt enter: group_id=${groupId} mid=${opts?.messageId ?? '<auto>'}`);
|
|
1344
|
+
try {
|
|
1345
|
+
const aid = this._currentAid();
|
|
1346
|
+
const secretData = await loadGroupSecret(this._keystoreRef, aid, groupId);
|
|
1347
|
+
if (!secretData) {
|
|
1348
|
+
throw new E2EEGroupSecretMissingError(`no group secret for ${groupId}`);
|
|
1349
|
+
}
|
|
1350
|
+
const identity = this._identityFn();
|
|
1351
|
+
const senderPkPem = identity?.private_key_pem ?? null;
|
|
1352
|
+
const senderCertPem = identity?.cert ?? null;
|
|
1353
|
+
const result = await encryptGroupMessage(groupId, secretData.epoch, secretData.secret, payload, {
|
|
1354
|
+
fromAid: aid,
|
|
1355
|
+
messageId: opts?.messageId ?? `gm-${uuidV4()}`,
|
|
1356
|
+
timestamp: opts?.timestamp ?? Date.now(),
|
|
1357
|
+
senderPrivateKeyPem: senderPkPem,
|
|
1358
|
+
senderCertPem,
|
|
1359
|
+
protectedHeaders: opts?.protectedHeaders ?? opts?.protected_headers ?? opts?.headers,
|
|
1360
|
+
context: opts?.context ?? null,
|
|
1361
|
+
});
|
|
1362
|
+
this._log.debug(`encrypt exit: elapsed=${Date.now() - tStart}ms group_id=${groupId} epoch=${secretData.epoch}`);
|
|
1363
|
+
return result;
|
|
1364
|
+
}
|
|
1365
|
+
catch (err) {
|
|
1366
|
+
this._log.debug(`encrypt exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
1367
|
+
throw err;
|
|
1313
1368
|
}
|
|
1314
|
-
const identity = this._identityFn();
|
|
1315
|
-
const senderPkPem = identity?.private_key_pem ?? null;
|
|
1316
|
-
const senderCertPem = identity?.cert ?? null;
|
|
1317
|
-
return encryptGroupMessage(groupId, secretData.epoch, secretData.secret, payload, {
|
|
1318
|
-
fromAid: aid,
|
|
1319
|
-
messageId: opts?.messageId ?? `gm-${uuidV4()}`,
|
|
1320
|
-
timestamp: opts?.timestamp ?? Date.now(),
|
|
1321
|
-
senderPrivateKeyPem: senderPkPem,
|
|
1322
|
-
senderCertPem,
|
|
1323
|
-
protectedHeaders: opts?.protectedHeaders ?? opts?.protected_headers ?? opts?.headers,
|
|
1324
|
-
context: opts?.context ?? null,
|
|
1325
|
-
});
|
|
1326
1369
|
}
|
|
1327
1370
|
/** 使用指定 epoch 加密群消息。 */
|
|
1328
1371
|
async encryptWithEpoch(groupId, epoch, payload, opts) {
|
|
1329
|
-
const
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1372
|
+
const tStart = Date.now();
|
|
1373
|
+
this._log.debug(`encryptWithEpoch enter: group_id=${groupId} epoch=${epoch} mid=${opts?.messageId ?? '<auto>'}`);
|
|
1374
|
+
try {
|
|
1375
|
+
const aid = this._currentAid();
|
|
1376
|
+
const secretData = await loadGroupSecret(this._keystoreRef, aid, groupId, epoch);
|
|
1377
|
+
if (!secretData) {
|
|
1378
|
+
throw new E2EEGroupSecretMissingError(`no group secret for ${groupId} epoch ${epoch}`);
|
|
1379
|
+
}
|
|
1380
|
+
const identity = this._identityFn();
|
|
1381
|
+
const senderPkPem = identity?.private_key_pem ?? null;
|
|
1382
|
+
if (!senderPkPem) {
|
|
1383
|
+
throw new E2EEError('sender identity private key unavailable for group message signing');
|
|
1384
|
+
}
|
|
1385
|
+
const senderCertPem = identity?.cert ?? null;
|
|
1386
|
+
const result = await encryptGroupMessage(groupId, secretData.epoch, secretData.secret, payload, {
|
|
1387
|
+
fromAid: aid,
|
|
1388
|
+
messageId: opts?.messageId ?? `gm-${uuidV4()}`,
|
|
1389
|
+
timestamp: opts?.timestamp ?? Date.now(),
|
|
1390
|
+
senderPrivateKeyPem: senderPkPem,
|
|
1391
|
+
senderCertPem,
|
|
1392
|
+
protectedHeaders: opts?.protectedHeaders ?? opts?.protected_headers ?? opts?.headers,
|
|
1393
|
+
context: opts?.context ?? null,
|
|
1394
|
+
});
|
|
1395
|
+
this._log.debug(`encryptWithEpoch exit: elapsed=${Date.now() - tStart}ms group_id=${groupId} epoch=${secretData.epoch}`);
|
|
1396
|
+
return result;
|
|
1333
1397
|
}
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
throw new E2EEError('sender identity private key unavailable for group message signing');
|
|
1398
|
+
catch (err) {
|
|
1399
|
+
this._log.debug(`encryptWithEpoch exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
1400
|
+
throw err;
|
|
1338
1401
|
}
|
|
1339
|
-
const senderCertPem = identity?.cert ?? null;
|
|
1340
|
-
return encryptGroupMessage(groupId, secretData.epoch, secretData.secret, payload, {
|
|
1341
|
-
fromAid: aid,
|
|
1342
|
-
messageId: opts?.messageId ?? `gm-${uuidV4()}`,
|
|
1343
|
-
timestamp: opts?.timestamp ?? Date.now(),
|
|
1344
|
-
senderPrivateKeyPem: senderPkPem,
|
|
1345
|
-
senderCertPem,
|
|
1346
|
-
protectedHeaders: opts?.protectedHeaders ?? opts?.protected_headers ?? opts?.headers,
|
|
1347
|
-
context: opts?.context ?? null,
|
|
1348
|
-
});
|
|
1349
1402
|
}
|
|
1350
1403
|
/**
|
|
1351
1404
|
* 解密单条群消息(异步)。内置防重放 + 发送方验签 + 外层字段校验。
|
|
@@ -1353,51 +1406,78 @@ export class GroupE2EEManager {
|
|
|
1353
1406
|
* opts.skipReplay: 跳过防重放检查(用于 group.pull 场景)。
|
|
1354
1407
|
*/
|
|
1355
1408
|
async decrypt(message, opts) {
|
|
1356
|
-
const
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
const aadMsgId = aad ? (aad.message_id ?? '') : '';
|
|
1366
|
-
const msgId = aadMsgId || (message.message_id ?? '');
|
|
1367
|
-
if (!skipReplay && groupId && sender && msgId) {
|
|
1368
|
-
// 返回原消息(不含 e2ee 字段),调用方可通过缺失 e2ee 识别 replay
|
|
1369
|
-
if (this._replayGuard.isSeen(groupId, sender, msgId))
|
|
1409
|
+
const tStart = Date.now();
|
|
1410
|
+
const groupIdInit = String(message.group_id ?? '');
|
|
1411
|
+
const senderInit = String(message.from ?? message.sender_aid ?? '');
|
|
1412
|
+
const midInit = String(message.message_id ?? '');
|
|
1413
|
+
this._log.debug(`decrypt enter: group_id=${groupIdInit} from=${senderInit} mid=${midInit} skip_replay=${!!opts?.skipReplay}`);
|
|
1414
|
+
try {
|
|
1415
|
+
const payload = isJsonObject(message.payload) ? message.payload : null;
|
|
1416
|
+
if (payload === null || payload.type !== 'e2ee.group_encrypted') {
|
|
1417
|
+
this._log.debug(`decrypt exit: elapsed=${Date.now() - tStart}ms result=passthrough_not_encrypted`);
|
|
1370
1418
|
return message;
|
|
1371
|
-
}
|
|
1372
|
-
// 解析发送方证书(零信任:无证书则拒绝)
|
|
1373
|
-
let senderCertPem = null;
|
|
1374
|
-
if (this._senderCertResolver && sender) {
|
|
1375
|
-
senderCertPem = this._senderCertResolver(sender);
|
|
1376
|
-
}
|
|
1377
|
-
if (!senderCertPem) {
|
|
1378
|
-
console.warn(`拒绝群消息:无法获取发送方 ${sender} 的证书(零信任模式禁止跳过验签): group=${groupId}`);
|
|
1379
|
-
return null;
|
|
1380
|
-
}
|
|
1381
|
-
const allSecrets = await loadAllGroupSecrets(this._keystoreRef, this._currentAid(), groupId);
|
|
1382
|
-
if (!allSecrets.size)
|
|
1383
|
-
return null;
|
|
1384
|
-
const result = await decryptGroupMessage(message, allSecrets, senderCertPem);
|
|
1385
|
-
// 解密成功后记录防重放
|
|
1386
|
-
if (result !== null) {
|
|
1387
|
-
const finalMsgId = aadMsgId || (message.message_id ?? '');
|
|
1388
|
-
if (!skipReplay && groupId && sender && finalMsgId) {
|
|
1389
|
-
this._replayGuard.record(groupId, sender, finalMsgId);
|
|
1390
1419
|
}
|
|
1420
|
+
const groupId = String(message.group_id ?? '');
|
|
1421
|
+
const sender = String(message.from ?? message.sender_aid ?? '');
|
|
1422
|
+
const skipReplay = opts?.skipReplay ?? false;
|
|
1423
|
+
// 防重放预检:优先使用 AAD 内 message_id
|
|
1424
|
+
const aad = isJsonObject(payload.aad) ? payload.aad : undefined;
|
|
1425
|
+
const aadMsgId = aad ? (aad.message_id ?? '') : '';
|
|
1426
|
+
const msgId = aadMsgId || (message.message_id ?? '');
|
|
1427
|
+
if (!skipReplay && groupId && sender && msgId) {
|
|
1428
|
+
// 返回原消息(不含 e2ee 字段),调用方可通过缺失 e2ee 识别 replay
|
|
1429
|
+
if (this._replayGuard.isSeen(groupId, sender, msgId)) {
|
|
1430
|
+
this._log.debug(`decrypt exit: elapsed=${Date.now() - tStart}ms result=replay_skipped group_id=${groupId}`);
|
|
1431
|
+
return message;
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
// 解析发送方证书(零信任:无证书则拒绝)
|
|
1435
|
+
let senderCertPem = null;
|
|
1436
|
+
if (this._senderCertResolver && sender) {
|
|
1437
|
+
senderCertPem = this._senderCertResolver(sender);
|
|
1438
|
+
}
|
|
1439
|
+
if (!senderCertPem) {
|
|
1440
|
+
this._log.warn(`拒绝群消息:无法获取发送方 ${sender} 的证书(零信任模式禁止跳过验签): group=${groupId}`);
|
|
1441
|
+
this._log.debug(`decrypt exit: elapsed=${Date.now() - tStart}ms result=rejected_no_sender_cert`);
|
|
1442
|
+
return null;
|
|
1443
|
+
}
|
|
1444
|
+
const allSecrets = await loadAllGroupSecrets(this._keystoreRef, this._currentAid(), groupId);
|
|
1445
|
+
if (!allSecrets.size) {
|
|
1446
|
+
this._log.debug(`decrypt exit: elapsed=${Date.now() - tStart}ms result=no_secrets group_id=${groupId}`);
|
|
1447
|
+
return null;
|
|
1448
|
+
}
|
|
1449
|
+
const result = await decryptGroupMessage(message, allSecrets, senderCertPem);
|
|
1450
|
+
// 解密成功后记录防重放
|
|
1451
|
+
if (result !== null) {
|
|
1452
|
+
const finalMsgId = aadMsgId || (message.message_id ?? '');
|
|
1453
|
+
if (!skipReplay && groupId && sender && finalMsgId) {
|
|
1454
|
+
this._replayGuard.record(groupId, sender, finalMsgId);
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
this._log.debug(`decrypt exit: elapsed=${Date.now() - tStart}ms result=${result !== null ? 'ok' : 'failed'} group_id=${groupId}`);
|
|
1458
|
+
return result;
|
|
1459
|
+
}
|
|
1460
|
+
catch (err) {
|
|
1461
|
+
this._log.debug(`decrypt exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
1462
|
+
throw err;
|
|
1391
1463
|
}
|
|
1392
|
-
return result;
|
|
1393
1464
|
}
|
|
1394
1465
|
/** 批量解密 */
|
|
1395
1466
|
async decryptBatch(messages, opts) {
|
|
1396
|
-
const
|
|
1397
|
-
|
|
1398
|
-
|
|
1467
|
+
const tStart = Date.now();
|
|
1468
|
+
this._log.debug(`decryptBatch enter: count=${messages.length} skip_replay=${!!opts?.skipReplay}`);
|
|
1469
|
+
try {
|
|
1470
|
+
const results = [];
|
|
1471
|
+
for (const m of messages) {
|
|
1472
|
+
results.push((await this.decrypt(m, opts)) ?? m);
|
|
1473
|
+
}
|
|
1474
|
+
this._log.debug(`decryptBatch exit: elapsed=${Date.now() - tStart}ms count=${results.length}`);
|
|
1475
|
+
return results;
|
|
1476
|
+
}
|
|
1477
|
+
catch (err) {
|
|
1478
|
+
this._log.debug(`decryptBatch exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
1479
|
+
throw err;
|
|
1399
1480
|
}
|
|
1400
|
-
return results;
|
|
1401
1481
|
}
|
|
1402
1482
|
// ── 密钥协议消息处理 ──────────────────────────────
|
|
1403
1483
|
/**
|
|
@@ -1408,60 +1488,89 @@ export class GroupE2EEManager {
|
|
|
1408
1488
|
* 返回 null 表示不是密钥消息。
|
|
1409
1489
|
*/
|
|
1410
1490
|
async handleIncoming(payload) {
|
|
1491
|
+
const tStart = Date.now();
|
|
1411
1492
|
const msgType = (payload.type ?? '');
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1493
|
+
this._log.debug(`handleIncoming enter: type=${msgType} group_id=${String(payload.group_id ?? '')}`);
|
|
1494
|
+
try {
|
|
1495
|
+
const aid = this._currentAid();
|
|
1496
|
+
if (msgType === 'e2ee.group_key_distribution') {
|
|
1497
|
+
// 解析发起者证书用于 manifest 验证
|
|
1498
|
+
let initiatorCert = null;
|
|
1499
|
+
const distributedBy = (payload.distributed_by ?? '');
|
|
1500
|
+
if (this._initiatorCertResolver && distributedBy) {
|
|
1501
|
+
initiatorCert = this._initiatorCertResolver(distributedBy);
|
|
1502
|
+
}
|
|
1503
|
+
const ok = await handleKeyDistribution(payload, this._keystoreRef, aid, initiatorCert);
|
|
1504
|
+
const r = ok ? 'distribution' : 'distribution_rejected';
|
|
1505
|
+
this._log.debug(`handleIncoming exit: elapsed=${Date.now() - tStart}ms result=${r}`);
|
|
1506
|
+
return r;
|
|
1419
1507
|
}
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1508
|
+
if (msgType === 'e2ee.group_key_response') {
|
|
1509
|
+
const pendingKey = `${String(payload.group_id ?? '')}:${String(payload.epoch ?? '')}:${String(payload.request_id ?? '')}`;
|
|
1510
|
+
const expected = this._pendingKeyRequests.get(pendingKey) ?? null;
|
|
1511
|
+
if (expected === null) {
|
|
1512
|
+
this._log.debug(`handleIncoming exit: elapsed=${Date.now() - tStart}ms result=response_rejected reason=no_pending`);
|
|
1513
|
+
return 'response_rejected';
|
|
1514
|
+
}
|
|
1515
|
+
const responderAid = String(payload.responder_aid ?? '');
|
|
1516
|
+
const responderCertPem = responderAid && this._initiatorCertResolver
|
|
1517
|
+
? this._initiatorCertResolver(responderAid)
|
|
1518
|
+
: null;
|
|
1519
|
+
const ok = await handleKeyResponse(payload, this._keystoreRef, aid, {
|
|
1520
|
+
expectedRequest: expected,
|
|
1521
|
+
responderCertPem,
|
|
1522
|
+
currentMembers: await this.getMemberAids(String(payload.group_id ?? '')),
|
|
1523
|
+
strict: true,
|
|
1524
|
+
});
|
|
1525
|
+
if (ok && expected)
|
|
1526
|
+
this._pendingKeyRequests.delete(pendingKey);
|
|
1527
|
+
const r = ok ? 'response' : 'response_rejected';
|
|
1528
|
+
this._log.debug(`handleIncoming exit: elapsed=${Date.now() - tStart}ms result=${r}`);
|
|
1529
|
+
return r;
|
|
1530
|
+
}
|
|
1531
|
+
if (msgType === 'e2ee.group_key_request') {
|
|
1532
|
+
this._log.debug(`handleIncoming exit: elapsed=${Date.now() - tStart}ms result=request`);
|
|
1533
|
+
return 'request';
|
|
1534
|
+
}
|
|
1535
|
+
this._log.debug(`handleIncoming exit: elapsed=${Date.now() - tStart}ms result=null`);
|
|
1536
|
+
return null;
|
|
1441
1537
|
}
|
|
1442
|
-
|
|
1443
|
-
|
|
1538
|
+
catch (err) {
|
|
1539
|
+
this._log.debug(`handleIncoming exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
1540
|
+
throw err;
|
|
1444
1541
|
}
|
|
1445
|
-
return null;
|
|
1446
1542
|
}
|
|
1447
1543
|
/** 构建恢复请求。返回 {to, payload} 或 null(限流/无目标)。 */
|
|
1448
1544
|
async buildRecoveryRequest(groupId, epoch, opts) {
|
|
1449
|
-
const
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1545
|
+
const tStart = Date.now();
|
|
1546
|
+
this._log.debug(`buildRecoveryRequest enter: group_id=${groupId} epoch=${epoch} sender_hint=${opts?.senderAid ?? ''}`);
|
|
1547
|
+
try {
|
|
1548
|
+
const aid = this._currentAid();
|
|
1549
|
+
if (!this._requestThrottle.allow(`request:${groupId}:${epoch}`)) {
|
|
1550
|
+
this._log.debug(`buildRecoveryRequest exit: elapsed=${Date.now() - tStart}ms result=throttled`);
|
|
1551
|
+
return null;
|
|
1552
|
+
}
|
|
1553
|
+
let candidates = [];
|
|
1554
|
+
const secretData = await loadGroupSecret(this._keystoreRef, aid, groupId);
|
|
1555
|
+
if (secretData?.member_aids?.length) {
|
|
1556
|
+
candidates = secretData.member_aids.filter(m => m !== aid);
|
|
1557
|
+
}
|
|
1558
|
+
if (!candidates.length && opts?.senderAid && opts.senderAid !== aid) {
|
|
1559
|
+
candidates = [opts.senderAid];
|
|
1560
|
+
}
|
|
1561
|
+
if (!candidates.length) {
|
|
1562
|
+
this._log.debug(`buildRecoveryRequest exit: elapsed=${Date.now() - tStart}ms result=no_candidates`);
|
|
1563
|
+
return null;
|
|
1564
|
+
}
|
|
1565
|
+
const payload = buildKeyRequest(groupId, epoch, aid);
|
|
1566
|
+
this.rememberKeyRequest(payload, candidates[0]);
|
|
1567
|
+
this._log.debug(`buildRecoveryRequest exit: elapsed=${Date.now() - tStart}ms target=${candidates[0]}`);
|
|
1568
|
+
return { to: candidates[0], payload };
|
|
1456
1569
|
}
|
|
1457
|
-
|
|
1458
|
-
|
|
1570
|
+
catch (err) {
|
|
1571
|
+
this._log.debug(`buildRecoveryRequest exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
1572
|
+
throw err;
|
|
1459
1573
|
}
|
|
1460
|
-
if (!candidates.length)
|
|
1461
|
-
return null;
|
|
1462
|
-
const payload = buildKeyRequest(groupId, epoch, aid);
|
|
1463
|
-
this.rememberKeyRequest(payload, candidates[0]);
|
|
1464
|
-
return { to: candidates[0], payload };
|
|
1465
1574
|
}
|
|
1466
1575
|
rememberKeyRequest(payload, expectedResponderAid) {
|
|
1467
1576
|
if (payload.type !== 'e2ee.group_key_request')
|
|
@@ -1473,21 +1582,38 @@ export class GroupE2EEManager {
|
|
|
1473
1582
|
}
|
|
1474
1583
|
/** 处理密钥请求(受频率限制 + 成员资格验证) */
|
|
1475
1584
|
async handleKeyRequestMsg(requestPayload, currentMembers) {
|
|
1585
|
+
const tStart = Date.now();
|
|
1476
1586
|
const requester = (requestPayload.requester_aid ?? '');
|
|
1477
1587
|
const groupId = (requestPayload.group_id ?? '');
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1588
|
+
this._log.debug(`handleKeyRequestMsg enter: group_id=${groupId} requester=${requester} members=${currentMembers.length}`);
|
|
1589
|
+
try {
|
|
1590
|
+
if (!requester || !groupId) {
|
|
1591
|
+
this._log.debug(`handleKeyRequestMsg exit: elapsed=${Date.now() - tStart}ms result=invalid_payload`);
|
|
1592
|
+
return null;
|
|
1593
|
+
}
|
|
1594
|
+
if (!currentMembers.includes(requester)) {
|
|
1595
|
+
this._log.warn(`reject key recover request: ${requester} not in group ${groupId} current member list`);
|
|
1596
|
+
this._log.debug(`handleKeyRequestMsg exit: elapsed=${Date.now() - tStart}ms result=not_member`);
|
|
1597
|
+
return null;
|
|
1598
|
+
}
|
|
1599
|
+
if (!this._responseThrottle.allow(`response:${groupId}:${requester}`)) {
|
|
1600
|
+
this._log.debug(`handleKeyRequestMsg exit: elapsed=${Date.now() - tStart}ms result=throttled`);
|
|
1601
|
+
return null;
|
|
1602
|
+
}
|
|
1603
|
+
const identity = this._identityFn();
|
|
1604
|
+
const privateKeyPem = identity?.private_key_pem;
|
|
1605
|
+
if (!privateKeyPem) {
|
|
1606
|
+
this._log.debug(`handleKeyRequestMsg exit: elapsed=${Date.now() - tStart}ms result=no_private_key`);
|
|
1607
|
+
return null;
|
|
1608
|
+
}
|
|
1609
|
+
const result = await handleKeyRequest(requestPayload, this._keystoreRef, this._currentAid(), currentMembers, privateKeyPem);
|
|
1610
|
+
this._log.debug(`handleKeyRequestMsg exit: elapsed=${Date.now() - tStart}ms result=${result !== null ? 'ok' : 'null'}`);
|
|
1611
|
+
return result;
|
|
1612
|
+
}
|
|
1613
|
+
catch (err) {
|
|
1614
|
+
this._log.debug(`handleKeyRequestMsg exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
1615
|
+
throw err;
|
|
1483
1616
|
}
|
|
1484
|
-
if (!this._responseThrottle.allow(`response:${groupId}:${requester}`))
|
|
1485
|
-
return null;
|
|
1486
|
-
const identity = this._identityFn();
|
|
1487
|
-
const privateKeyPem = identity?.private_key_pem;
|
|
1488
|
-
if (!privateKeyPem)
|
|
1489
|
-
return null;
|
|
1490
|
-
return handleKeyRequest(requestPayload, this._keystoreRef, this._currentAid(), currentMembers, privateKeyPem);
|
|
1491
1617
|
}
|
|
1492
1618
|
// ── 状态查询 ──────────────────────────────────────
|
|
1493
1619
|
async hasSecret(groupId) {
|