@agentunion/fastaun 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.js +306 -209
- package/dist/auth.js.map +1 -1
- package/dist/client.js +1041 -704
- package/dist/client.js.map +1 -1
- package/dist/discovery.d.ts +3 -0
- package/dist/discovery.js +29 -2
- package/dist/discovery.js.map +1 -1
- package/dist/e2ee-group.d.ts +2 -1
- package/dist/e2ee-group.js +207 -56
- package/dist/e2ee-group.js.map +1 -1
- package/dist/e2ee.js +24 -9
- package/dist/e2ee.js.map +1 -1
- package/dist/events.js +1 -1
- package/dist/events.js.map +1 -1
- package/dist/keystore/aid-db.d.ts +7 -1
- package/dist/keystore/aid-db.js +10 -3
- package/dist/keystore/aid-db.js.map +1 -1
- package/dist/keystore/file.js +12 -9
- package/dist/keystore/file.js.map +1 -1
- package/dist/keystore/sqlite-backup.js +5 -5
- package/dist/keystore/sqlite-backup.js.map +1 -1
- package/dist/logger.d.ts +2 -0
- package/dist/logger.js +69 -13
- package/dist/logger.js.map +1 -1
- package/dist/namespaces/auth.d.ts +1 -0
- package/dist/namespaces/auth.js +289 -146
- package/dist/namespaces/auth.js.map +1 -1
- package/dist/namespaces/custody.d.ts +1 -0
- package/dist/namespaces/custody.js +138 -56
- package/dist/namespaces/custody.js.map +1 -1
- package/dist/namespaces/meta.d.ts +1 -0
- package/dist/namespaces/meta.js +26 -0
- package/dist/namespaces/meta.js.map +1 -1
- package/dist/secret-store/file-store.js +3 -2
- package/dist/secret-store/file-store.js.map +1 -1
- package/dist/transport.js +83 -2
- package/dist/transport.js.map +1 -1
- package/package.json +1 -1
package/dist/e2ee-group.js
CHANGED
|
@@ -460,17 +460,26 @@ export function encryptGroupMessage(groupId, epoch, groupSecret, payload, opts)
|
|
|
460
460
|
*/
|
|
461
461
|
export function decryptGroupMessage(message, groupSecrets, senderCertPem, opts) {
|
|
462
462
|
const requireSignature = opts?.requireSignature ?? true;
|
|
463
|
+
const logger = opts?.logger;
|
|
463
464
|
const payload = isJsonObject(message.payload) ? message.payload : null;
|
|
464
|
-
if (payload === null)
|
|
465
|
+
if (payload === null) {
|
|
466
|
+
logger?.warn(`decryptGroupMessage rejected: payload is not an object mid=${String(message.message_id ?? '')}`);
|
|
465
467
|
return null;
|
|
466
|
-
|
|
468
|
+
}
|
|
469
|
+
if (payload.type !== 'e2ee.group_encrypted') {
|
|
470
|
+
logger?.warn(`decryptGroupMessage rejected: unexpected payload.type=${String(payload.type)} mid=${String(message.message_id ?? '')}`);
|
|
467
471
|
return null;
|
|
472
|
+
}
|
|
468
473
|
const epoch = payload.epoch;
|
|
469
|
-
if (epoch == null)
|
|
474
|
+
if (epoch == null) {
|
|
475
|
+
logger?.warn(`decryptGroupMessage rejected: missing epoch mid=${String(message.message_id ?? '')}`);
|
|
470
476
|
return null;
|
|
477
|
+
}
|
|
471
478
|
const groupSecret = groupSecrets.get(epoch);
|
|
472
|
-
if (!groupSecret)
|
|
479
|
+
if (!groupSecret) {
|
|
480
|
+
logger?.warn(`decryptGroupMessage rejected: no group secret for epoch=${epoch} mid=${String(message.message_id ?? '')}`);
|
|
473
481
|
return null;
|
|
482
|
+
}
|
|
474
483
|
try {
|
|
475
484
|
// 优先从 AAD 读取 group_id 和 message_id
|
|
476
485
|
const aad = isJsonObject(payload.aad) ? payload.aad : undefined;
|
|
@@ -483,34 +492,44 @@ export function decryptGroupMessage(message, groupSecrets, senderCertPem, opts)
|
|
|
483
492
|
messageId = aad.message_id || message.message_id || '';
|
|
484
493
|
aadFrom = aad.from || '';
|
|
485
494
|
// 外层路由字段与 AAD 绑定校验
|
|
486
|
-
if (outerGroupId && groupId !== outerGroupId)
|
|
495
|
+
if (outerGroupId && groupId !== outerGroupId) {
|
|
496
|
+
logger?.warn(`decryptGroupMessage rejected: outer group_id does not match aad.group_id outer=${outerGroupId} aad=${groupId}`);
|
|
487
497
|
return null;
|
|
498
|
+
}
|
|
488
499
|
if (aadFrom) {
|
|
489
500
|
const outerFrom = message.from || '';
|
|
490
501
|
const outerSender = message.sender_aid || '';
|
|
491
|
-
if (outerFrom && outerFrom !== aadFrom)
|
|
502
|
+
if (outerFrom && outerFrom !== aadFrom) {
|
|
503
|
+
logger?.warn(`decryptGroupMessage rejected: outer from does not match aad.from outer=${outerFrom} aad=${aadFrom}`);
|
|
492
504
|
return null;
|
|
493
|
-
|
|
505
|
+
}
|
|
506
|
+
if (outerSender && outerSender !== aadFrom) {
|
|
507
|
+
logger?.warn(`decryptGroupMessage rejected: outer sender_aid does not match aad.from outer=${outerSender} aad=${aadFrom}`);
|
|
494
508
|
return null;
|
|
509
|
+
}
|
|
495
510
|
}
|
|
496
511
|
}
|
|
497
512
|
else {
|
|
498
513
|
groupId = outerGroupId;
|
|
499
514
|
messageId = message.message_id || '';
|
|
500
515
|
}
|
|
501
|
-
if (!groupId || !messageId)
|
|
516
|
+
if (!groupId || !messageId) {
|
|
517
|
+
logger?.warn(`decryptGroupMessage rejected: missing group_id or message_id group=${groupId} mid=${messageId}`);
|
|
502
518
|
return null;
|
|
519
|
+
}
|
|
503
520
|
const msgKey = deriveGroupMsgKey(groupSecret, groupId, messageId);
|
|
504
521
|
const nonce = Buffer.from(payload.nonce, 'base64');
|
|
505
522
|
const ciphertext = Buffer.from(payload.ciphertext, 'base64');
|
|
506
523
|
const tag = Buffer.from(payload.tag, 'base64');
|
|
507
524
|
if (!verifyEnvelopeMetadataAuth(payload, msgKey)) {
|
|
525
|
+
logger?.warn(`decryptGroupMessage rejected: envelope metadata auth failed group=${groupId} mid=${messageId}`);
|
|
508
526
|
return null;
|
|
509
527
|
}
|
|
510
528
|
const aadBytes = aad ? aadBytesGroup(aad) : Buffer.alloc(0);
|
|
511
529
|
const plaintext = aesGcmDecrypt(msgKey, ciphertext, tag, nonce, aadBytes);
|
|
512
530
|
const decoded = JSON.parse(plaintext.toString('utf-8'));
|
|
513
531
|
if (!validateDecryptedEnvelopeMetadata(decoded, payload, message)) {
|
|
532
|
+
logger?.warn(`decryptGroupMessage rejected: decrypted envelope metadata validation failed group=${groupId} mid=${messageId}`);
|
|
514
533
|
return null;
|
|
515
534
|
}
|
|
516
535
|
const e2ee = {
|
|
@@ -534,43 +553,56 @@ export function decryptGroupMessage(message, groupSecrets, senderCertPem, opts)
|
|
|
534
553
|
// 发送方签名验证
|
|
535
554
|
const senderSigB64 = payload.sender_signature;
|
|
536
555
|
if (requireSignature) {
|
|
537
|
-
if (!senderSigB64)
|
|
556
|
+
if (!senderSigB64) {
|
|
557
|
+
logger?.warn(`decryptGroupMessage rejected: sender signature required but missing group=${groupId} mid=${messageId}`);
|
|
538
558
|
return null;
|
|
539
|
-
|
|
559
|
+
}
|
|
560
|
+
if (!senderCertPem) {
|
|
561
|
+
logger?.warn(`decryptGroupMessage rejected: sender signature required but senderCertPem missing group=${groupId} mid=${messageId}`);
|
|
540
562
|
return null;
|
|
563
|
+
}
|
|
541
564
|
try {
|
|
542
565
|
const senderPub = pemToCertPublicKey(senderCertPem);
|
|
543
566
|
const sigBytes = Buffer.from(senderSigB64, 'base64');
|
|
544
567
|
const verifyPayload = Buffer.concat([ciphertext, tag, aadBytes]);
|
|
545
|
-
if (!ecdsaVerify(senderPub, sigBytes, verifyPayload))
|
|
568
|
+
if (!ecdsaVerify(senderPub, sigBytes, verifyPayload)) {
|
|
569
|
+
logger?.warn(`decryptGroupMessage rejected: sender signature verification failed group=${groupId} mid=${messageId}`);
|
|
546
570
|
return null;
|
|
571
|
+
}
|
|
547
572
|
if (isJsonObject(result.e2ee))
|
|
548
573
|
result.e2ee.sender_verified = true;
|
|
549
574
|
}
|
|
550
|
-
catch {
|
|
575
|
+
catch (exc) {
|
|
576
|
+
logger?.warn(`decryptGroupMessage rejected: sender signature verification threw group=${groupId} mid=${messageId} err=${String(exc)}`);
|
|
551
577
|
return null;
|
|
552
578
|
}
|
|
553
579
|
}
|
|
554
580
|
else if (senderCertPem) {
|
|
555
581
|
// 非零信任模式但提供了证书:有证书时强制验签
|
|
556
|
-
if (!senderSigB64)
|
|
582
|
+
if (!senderSigB64) {
|
|
583
|
+
logger?.warn(`decryptGroupMessage rejected: senderCertPem present but signature missing group=${groupId} mid=${messageId}`);
|
|
557
584
|
return null;
|
|
585
|
+
}
|
|
558
586
|
try {
|
|
559
587
|
const senderPub = pemToCertPublicKey(senderCertPem);
|
|
560
588
|
const sigBytes = Buffer.from(senderSigB64, 'base64');
|
|
561
589
|
const verifyPayload = Buffer.concat([ciphertext, tag, aadBytes]);
|
|
562
|
-
if (!ecdsaVerify(senderPub, sigBytes, verifyPayload))
|
|
590
|
+
if (!ecdsaVerify(senderPub, sigBytes, verifyPayload)) {
|
|
591
|
+
logger?.warn(`decryptGroupMessage rejected: optional sender signature verification failed group=${groupId} mid=${messageId}`);
|
|
563
592
|
return null;
|
|
593
|
+
}
|
|
564
594
|
if (isJsonObject(result.e2ee))
|
|
565
595
|
result.e2ee.sender_verified = true;
|
|
566
596
|
}
|
|
567
|
-
catch {
|
|
597
|
+
catch (exc) {
|
|
598
|
+
logger?.warn(`decryptGroupMessage rejected: optional sender signature verification threw group=${groupId} mid=${messageId} err=${String(exc)}`);
|
|
568
599
|
return null;
|
|
569
600
|
}
|
|
570
601
|
}
|
|
571
602
|
return result;
|
|
572
603
|
}
|
|
573
|
-
catch {
|
|
604
|
+
catch (exc) {
|
|
605
|
+
logger?.warn(`decryptGroupMessage rejected: decrypt threw mid=${String(message.message_id ?? '')} err=${String(exc)}`);
|
|
574
606
|
return null;
|
|
575
607
|
}
|
|
576
608
|
}
|
|
@@ -753,7 +785,7 @@ function assessIncomingEpochChain(keystore, aid, groupId, epoch, commitment, inc
|
|
|
753
785
|
const rid = rotationId.trim();
|
|
754
786
|
const rotator = rotatorAid.trim();
|
|
755
787
|
if (rid && !chain) {
|
|
756
|
-
logger.warn(`[e2ee-group]
|
|
788
|
+
logger.warn(`[e2ee-group] rejecting rotation key missing epoch_chain source=${source} group=${groupId} epoch=${epoch} rotation=${rid}`);
|
|
757
789
|
return { ok: false };
|
|
758
790
|
}
|
|
759
791
|
const current = loadGroupSecret(keystore, aid, groupId);
|
|
@@ -764,7 +796,7 @@ function assessIncomingEpochChain(keystore, aid, groupId, epoch, commitment, inc
|
|
|
764
796
|
return { ok: true };
|
|
765
797
|
if (rid && chain && currentChain && currentChain !== chain) {
|
|
766
798
|
if (!(currentPendingRotationId && currentPendingRotationId !== rid)) {
|
|
767
|
-
logger.warn(`[e2ee-group]
|
|
799
|
+
logger.warn(`[e2ee-group] rejecting same-epoch chain fork source=${source} group=${groupId} epoch=${epoch} rotation=${rid}`);
|
|
768
800
|
return { ok: false };
|
|
769
801
|
}
|
|
770
802
|
}
|
|
@@ -777,17 +809,17 @@ function assessIncomingEpochChain(keystore, aid, groupId, epoch, commitment, inc
|
|
|
777
809
|
return { ok: true, unverified: true, reason: 'missing_prev_chain' };
|
|
778
810
|
if (!rotator) {
|
|
779
811
|
if (rid) {
|
|
780
|
-
logger.warn(`[e2ee-group]
|
|
812
|
+
logger.warn(`[e2ee-group] rejecting rotation key missing rotator_aid source=${source} group=${groupId} epoch=${epoch} rotation=${rid}`);
|
|
781
813
|
return { ok: false };
|
|
782
814
|
}
|
|
783
815
|
return { ok: true, unverified: true, reason: 'missing_rotator_aid' };
|
|
784
816
|
}
|
|
785
817
|
if (!verifyEpochChain(chain, prevChain, epoch, commitment, rotator)) {
|
|
786
818
|
if (rid) {
|
|
787
|
-
logger.warn(`[e2ee-group]
|
|
819
|
+
logger.warn(`[e2ee-group] rejecting rotation key with failed epoch_chain verification source=${source} group=${groupId} epoch=${epoch} rotation=${rid}`);
|
|
788
820
|
return { ok: false };
|
|
789
821
|
}
|
|
790
|
-
logger.warn(`[e2ee-group] epoch_chain
|
|
822
|
+
logger.warn(`[e2ee-group] epoch_chain verification failed, accepting as unverified for compatibility source=${source} group=${groupId} epoch=${epoch}`);
|
|
791
823
|
return { ok: true, unverified: true, reason: 'chain_mismatch_legacy' };
|
|
792
824
|
}
|
|
793
825
|
if (!rid)
|
|
@@ -924,40 +956,58 @@ export function handleKeyDistribution(message, keystore, aid, initiatorCertPem,
|
|
|
924
956
|
const groupSecretB64 = payload.group_secret;
|
|
925
957
|
const commitment = payload.commitment;
|
|
926
958
|
const memberAids = payload.member_aids ?? [];
|
|
927
|
-
|
|
959
|
+
logger?.debug(`handleKeyDistribution enter: group=${String(groupId ?? '')}, epoch=${String(epoch ?? '')}, aid=${aid}, members=${memberAids.length}`);
|
|
960
|
+
if (!groupId || epoch == null || !groupSecretB64 || !commitment) {
|
|
961
|
+
logger?.warn(`handleKeyDistribution rejected: missing required fields group=${String(groupId ?? '')} epoch=${String(epoch ?? '')} has_secret=${!!groupSecretB64} has_commitment=${!!commitment}`);
|
|
928
962
|
return false;
|
|
963
|
+
}
|
|
929
964
|
// 验证 Membership Manifest 签名
|
|
930
965
|
const manifest = isJsonObject(payload.manifest) ? payload.manifest : undefined;
|
|
931
966
|
if (initiatorCertPem) {
|
|
932
|
-
if (!manifest)
|
|
967
|
+
if (!manifest) {
|
|
968
|
+
logger?.warn(`handleKeyDistribution rejected: manifest required when initiatorCertPem present group=${groupId} epoch=${epoch}`);
|
|
933
969
|
return false;
|
|
934
|
-
|
|
970
|
+
}
|
|
971
|
+
if (!verifyMembershipManifest(manifest, initiatorCertPem)) {
|
|
972
|
+
logger?.warn(`handleKeyDistribution rejected: manifest signature verification failed group=${groupId} epoch=${epoch}`);
|
|
935
973
|
return false;
|
|
936
|
-
|
|
974
|
+
}
|
|
975
|
+
if (manifest.group_id !== groupId || manifest.epoch !== epoch) {
|
|
976
|
+
logger?.warn(`handleKeyDistribution rejected: manifest group_id/epoch mismatch group=${groupId} epoch=${epoch} manifest_group=${String(manifest.group_id)} manifest_epoch=${String(manifest.epoch)}`);
|
|
937
977
|
return false;
|
|
978
|
+
}
|
|
938
979
|
const manifestMembers = [...(manifest.member_aids ?? [])].sort();
|
|
939
980
|
const payloadMembers = [...memberAids].sort();
|
|
940
|
-
if (JSON.stringify(manifestMembers) !== JSON.stringify(payloadMembers))
|
|
981
|
+
if (JSON.stringify(manifestMembers) !== JSON.stringify(payloadMembers)) {
|
|
982
|
+
logger?.warn(`handleKeyDistribution rejected: manifest members mismatch payload members group=${groupId} epoch=${epoch}`);
|
|
941
983
|
return false;
|
|
984
|
+
}
|
|
942
985
|
}
|
|
943
986
|
else if (manifest) {
|
|
944
|
-
if (manifest.group_id !== groupId || manifest.epoch !== epoch)
|
|
987
|
+
if (manifest.group_id !== groupId || manifest.epoch !== epoch) {
|
|
988
|
+
logger?.warn(`handleKeyDistribution rejected: manifest group_id/epoch mismatch (no cert) group=${groupId} epoch=${epoch}`);
|
|
945
989
|
return false;
|
|
990
|
+
}
|
|
946
991
|
const manifestMembers = [...(manifest.member_aids ?? [])].sort();
|
|
947
992
|
const payloadMembers = [...memberAids].sort();
|
|
948
|
-
if (JSON.stringify(manifestMembers) !== JSON.stringify(payloadMembers))
|
|
993
|
+
if (JSON.stringify(manifestMembers) !== JSON.stringify(payloadMembers)) {
|
|
994
|
+
logger?.warn(`handleKeyDistribution rejected: manifest members mismatch payload members (no cert) group=${groupId} epoch=${epoch}`);
|
|
949
995
|
return false;
|
|
996
|
+
}
|
|
950
997
|
}
|
|
951
998
|
const groupSecret = Buffer.from(groupSecretB64, 'base64');
|
|
952
999
|
// 验证 commitment
|
|
953
1000
|
if (!verifyMembershipCommitment(commitment, memberAids, epoch, groupId, aid, groupSecret)) {
|
|
1001
|
+
logger?.warn(`handleKeyDistribution rejected: commitment verification failed group=${groupId} epoch=${epoch}`);
|
|
954
1002
|
return false;
|
|
955
1003
|
}
|
|
956
1004
|
const incomingChain = typeof payload.epoch_chain === 'string' ? payload.epoch_chain : undefined;
|
|
957
1005
|
const rotationId = typeof payload.rotation_id === 'string' ? payload.rotation_id : '';
|
|
958
1006
|
const chainAssessment = assessIncomingEpochChain(keystore, aid, groupId, epoch, commitment, incomingChain, rotationId, String(payload.distributed_by ?? payload.rotator_aid ?? ''), 'key_distribution', logger);
|
|
959
|
-
if (!chainAssessment.ok)
|
|
1007
|
+
if (!chainAssessment.ok) {
|
|
1008
|
+
logger?.warn(`handleKeyDistribution rejected: epoch chain assessment failed group=${groupId} epoch=${epoch} reason=${chainAssessment.reason ?? ''}`);
|
|
960
1009
|
return false;
|
|
1010
|
+
}
|
|
961
1011
|
return storeGroupSecret(keystore, aid, groupId, epoch, groupSecret, commitment, memberAids, incomingChain, rotationId, chainAssessment.unverified, chainAssessment.reason);
|
|
962
1012
|
}
|
|
963
1013
|
/** 构建密钥请求 payload */
|
|
@@ -972,26 +1022,34 @@ export function buildKeyRequest(groupId, epoch, requesterAid, requestId) {
|
|
|
972
1022
|
};
|
|
973
1023
|
}
|
|
974
1024
|
/** 处理收到的密钥请求 */
|
|
975
|
-
export function handleKeyRequest(request, keystore, aid, currentMembers, privateKeyPem) {
|
|
1025
|
+
export function handleKeyRequest(request, keystore, aid, currentMembers, privateKeyPem, logger) {
|
|
976
1026
|
const payload = 'group_id' in request
|
|
977
1027
|
? request
|
|
978
1028
|
: (isJsonObject(request.payload) ? request.payload : request);
|
|
979
1029
|
const requesterAid = payload.requester_aid;
|
|
980
1030
|
const groupId = payload.group_id;
|
|
981
1031
|
const epoch = payload.epoch;
|
|
982
|
-
|
|
1032
|
+
logger?.debug(`handleKeyRequest enter: group=${String(groupId ?? '')}, epoch=${String(epoch ?? '')}, requester=${String(requesterAid ?? '')}, aid=${aid}`);
|
|
1033
|
+
if (!requesterAid || !groupId || epoch == null) {
|
|
1034
|
+
logger?.warn(`handleKeyRequest rejected: missing required fields requester=${String(requesterAid ?? '')} group=${String(groupId ?? '')} epoch=${String(epoch ?? '')}`);
|
|
983
1035
|
return null;
|
|
1036
|
+
}
|
|
984
1037
|
// 验证请求者是群成员
|
|
985
|
-
if (!currentMembers.includes(requesterAid))
|
|
1038
|
+
if (!currentMembers.includes(requesterAid)) {
|
|
1039
|
+
logger?.warn(`handleKeyRequest rejected: requester not in current members group=${groupId} requester=${requesterAid}`);
|
|
986
1040
|
return null;
|
|
1041
|
+
}
|
|
987
1042
|
// 查本地密钥
|
|
988
1043
|
const secretData = loadGroupSecret(keystore, aid, groupId, epoch);
|
|
989
|
-
if (!secretData)
|
|
1044
|
+
if (!secretData) {
|
|
1045
|
+
logger?.warn(`handleKeyRequest rejected: no local secret for epoch group=${groupId} epoch=${epoch}`);
|
|
990
1046
|
return null;
|
|
1047
|
+
}
|
|
991
1048
|
// 历史隔离:密钥存储中记录了该 epoch 的成员列表,
|
|
992
1049
|
// 若请求者不在该列表中,说明其不属于该 epoch,直接拒绝(不响应)。
|
|
993
1050
|
const memberAids = (secretData.member_aids ?? []).map(String).filter(Boolean).sort();
|
|
994
1051
|
if (memberAids.length > 0 && !memberAids.includes(requesterAid)) {
|
|
1052
|
+
logger?.warn(`handleKeyRequest rejected: requester not in epoch member list group=${groupId} epoch=${epoch} requester=${requesterAid}`);
|
|
995
1053
|
return null;
|
|
996
1054
|
}
|
|
997
1055
|
// 确定响应使用的成员列表:优先使用密钥存储中的成员列表,
|
|
@@ -1030,55 +1088,84 @@ export function handleKeyResponse(response, keystore, aid, opts) {
|
|
|
1030
1088
|
const groupSecretB64 = payload.group_secret;
|
|
1031
1089
|
const commitment = payload.commitment;
|
|
1032
1090
|
const memberAids = payload.member_aids ?? [];
|
|
1033
|
-
|
|
1091
|
+
const logger = opts?.logger;
|
|
1092
|
+
logger?.debug(`handleKeyResponse enter: group=${String(groupId ?? '')}, epoch=${String(epoch ?? '')}, responder=${String(payload.responder_aid ?? '')}, aid=${aid}, strict=${!!opts?.strict}`);
|
|
1093
|
+
if (!groupId || epoch == null || !groupSecretB64 || !commitment) {
|
|
1094
|
+
logger?.warn(`handleKeyResponse rejected: missing required fields group=${String(groupId ?? '')} epoch=${String(epoch ?? '')} has_secret=${!!groupSecretB64} has_commitment=${!!commitment}`);
|
|
1034
1095
|
return false;
|
|
1096
|
+
}
|
|
1035
1097
|
const expected = opts?.expectedRequest ?? null;
|
|
1036
1098
|
if (expected) {
|
|
1037
|
-
if (payload.requester_aid !== aid)
|
|
1099
|
+
if (payload.requester_aid !== aid) {
|
|
1100
|
+
logger?.warn(`handleKeyResponse rejected: requester_aid mismatch group=${groupId} epoch=${epoch} got=${String(payload.requester_aid)} expected=${aid}`);
|
|
1038
1101
|
return false;
|
|
1102
|
+
}
|
|
1039
1103
|
const expectedResponder = String(expected._expected_responder_aid ?? '');
|
|
1040
|
-
if (expectedResponder && payload.responder_aid !== expectedResponder)
|
|
1104
|
+
if (expectedResponder && payload.responder_aid !== expectedResponder) {
|
|
1105
|
+
logger?.warn(`handleKeyResponse rejected: responder_aid mismatch group=${groupId} epoch=${epoch} got=${String(payload.responder_aid)} expected=${expectedResponder}`);
|
|
1041
1106
|
return false;
|
|
1042
|
-
|
|
1107
|
+
}
|
|
1108
|
+
if (payload.request_id !== expected.request_id) {
|
|
1109
|
+
logger?.warn(`handleKeyResponse rejected: request_id mismatch group=${groupId} epoch=${epoch}`);
|
|
1043
1110
|
return false;
|
|
1044
|
-
|
|
1111
|
+
}
|
|
1112
|
+
if (payload.group_id !== expected.group_id) {
|
|
1113
|
+
logger?.warn(`handleKeyResponse rejected: group_id mismatch with expected request`);
|
|
1045
1114
|
return false;
|
|
1046
|
-
|
|
1115
|
+
}
|
|
1116
|
+
if (Number(payload.epoch ?? 0) !== Number(expected.epoch ?? 0)) {
|
|
1117
|
+
logger?.warn(`handleKeyResponse rejected: epoch mismatch with expected request group=${groupId}`);
|
|
1047
1118
|
return false;
|
|
1119
|
+
}
|
|
1048
1120
|
}
|
|
1049
1121
|
const responderAid = String(payload.responder_aid ?? '');
|
|
1050
1122
|
if (opts?.strict) {
|
|
1051
|
-
if (!responderAid || !opts.responderCertPem)
|
|
1123
|
+
if (!responderAid || !opts.responderCertPem) {
|
|
1124
|
+
logger?.warn(`handleKeyResponse rejected (strict): missing responder aid or cert group=${groupId} epoch=${epoch}`);
|
|
1052
1125
|
return false;
|
|
1053
|
-
|
|
1126
|
+
}
|
|
1127
|
+
if ((opts.currentMembers?.length ?? 0) > 0 && !opts.currentMembers.includes(responderAid)) {
|
|
1128
|
+
logger?.warn(`handleKeyResponse rejected (strict): responder not in current members group=${groupId} epoch=${epoch} responder=${responderAid}`);
|
|
1054
1129
|
return false;
|
|
1055
|
-
|
|
1130
|
+
}
|
|
1131
|
+
if (!verifyGroupKeyResponseSignature(payload, opts.responderCertPem)) {
|
|
1132
|
+
logger?.warn(`handleKeyResponse rejected (strict): response signature verification failed group=${groupId} epoch=${epoch} responder=${responderAid}`);
|
|
1056
1133
|
return false;
|
|
1134
|
+
}
|
|
1057
1135
|
}
|
|
1058
1136
|
else if (opts?.responderCertPem && payload.response_signature) {
|
|
1059
|
-
if (!verifyGroupKeyResponseSignature(payload, opts.responderCertPem))
|
|
1137
|
+
if (!verifyGroupKeyResponseSignature(payload, opts.responderCertPem)) {
|
|
1138
|
+
logger?.warn(`handleKeyResponse rejected: response signature verification failed group=${groupId} epoch=${epoch} responder=${responderAid}`);
|
|
1060
1139
|
return false;
|
|
1140
|
+
}
|
|
1061
1141
|
}
|
|
1062
1142
|
const groupSecret = Buffer.from(groupSecretB64, 'base64');
|
|
1063
1143
|
if (!verifyMembershipCommitment(commitment, memberAids, epoch, groupId, aid, groupSecret)) {
|
|
1144
|
+
logger?.warn(`handleKeyResponse rejected: commitment verification failed group=${groupId} epoch=${epoch}`);
|
|
1064
1145
|
return false;
|
|
1065
1146
|
}
|
|
1066
1147
|
const manifest = isJsonObject(payload.manifest) ? payload.manifest : null;
|
|
1067
1148
|
if (manifest) {
|
|
1068
|
-
if (manifest.group_id !== groupId || manifest.epoch !== epoch)
|
|
1149
|
+
if (manifest.group_id !== groupId || manifest.epoch !== epoch) {
|
|
1150
|
+
logger?.warn(`handleKeyResponse rejected: manifest group_id/epoch mismatch group=${groupId} epoch=${epoch}`);
|
|
1069
1151
|
return false;
|
|
1152
|
+
}
|
|
1070
1153
|
const manifestMembers = Array.isArray(manifest.member_aids)
|
|
1071
1154
|
? manifest.member_aids.map((item) => String(item ?? '').trim()).filter(Boolean).sort()
|
|
1072
1155
|
: [];
|
|
1073
1156
|
const payloadMembers = memberAids.map((item) => String(item ?? '').trim()).filter(Boolean).sort();
|
|
1074
|
-
if (manifestMembers.length > 0 && manifestMembers.join('\n') !== payloadMembers.join('\n'))
|
|
1157
|
+
if (manifestMembers.length > 0 && manifestMembers.join('\n') !== payloadMembers.join('\n')) {
|
|
1158
|
+
logger?.warn(`handleKeyResponse rejected: manifest members mismatch payload members group=${groupId} epoch=${epoch}`);
|
|
1075
1159
|
return false;
|
|
1160
|
+
}
|
|
1076
1161
|
}
|
|
1077
1162
|
const incomingChain = typeof payload.epoch_chain === 'string' ? payload.epoch_chain : undefined;
|
|
1078
1163
|
const rotationId = typeof payload.rotation_id === 'string' ? payload.rotation_id : '';
|
|
1079
1164
|
const chainAssessment = assessIncomingEpochChain(keystore, aid, groupId, epoch, commitment, incomingChain, rotationId, String(payload.distributed_by ?? payload.rotator_aid ?? payload.responder_aid ?? ''), 'key_response', opts?.logger);
|
|
1080
|
-
if (!chainAssessment.ok)
|
|
1165
|
+
if (!chainAssessment.ok) {
|
|
1166
|
+
logger?.warn(`handleKeyResponse rejected: epoch chain assessment failed group=${groupId} epoch=${epoch} reason=${chainAssessment.reason ?? ''}`);
|
|
1081
1167
|
return false;
|
|
1168
|
+
}
|
|
1082
1169
|
return storeGroupSecretEpoch(keystore, aid, groupId, epoch, groupSecret, commitment, memberAids, incomingChain, rotationId, chainAssessment.unverified, chainAssessment.reason);
|
|
1083
1170
|
}
|
|
1084
1171
|
// ── GroupE2EEManager 类 ───────────────────────────────────────
|
|
@@ -1114,6 +1201,7 @@ export class GroupE2EEManager {
|
|
|
1114
1201
|
/** 创建首个 epoch。返回 {epoch, commitment, distributions: [{to, payload}]} */
|
|
1115
1202
|
createEpoch(groupId, memberAids) {
|
|
1116
1203
|
const aid = this._currentAid();
|
|
1204
|
+
this._logger.debug(`create first epoch: groupId=${groupId}, members=${memberAids.length}, aid=${aid}`);
|
|
1117
1205
|
const gs = generateGroupSecret();
|
|
1118
1206
|
const epoch = 1;
|
|
1119
1207
|
const commitment = computeMembershipCommitment(memberAids, epoch, groupId, gs);
|
|
@@ -1121,6 +1209,7 @@ export class GroupE2EEManager {
|
|
|
1121
1209
|
storeGroupSecret(this._keystore, aid, groupId, epoch, gs, commitment, memberAids, epochChain);
|
|
1122
1210
|
const manifest = this._signManifest(buildMembershipManifest(groupId, epoch, null, memberAids, { initiatorAid: aid }));
|
|
1123
1211
|
const distPayload = buildKeyDistribution(groupId, epoch, gs, memberAids, aid, manifest, epochChain);
|
|
1212
|
+
this._logger.debug(`create first epoch success: groupId=${groupId}, epoch=${epoch}`);
|
|
1124
1213
|
return {
|
|
1125
1214
|
epoch,
|
|
1126
1215
|
commitment,
|
|
@@ -1135,16 +1224,19 @@ export class GroupE2EEManager {
|
|
|
1135
1224
|
const current = loadGroupSecret(this._keystore, aid, groupId);
|
|
1136
1225
|
const prevEpoch = current ? Number(current.epoch) : null;
|
|
1137
1226
|
const newEpoch = (prevEpoch ?? 0) + 1;
|
|
1227
|
+
this._logger.debug(`rotateEpoch start: groupId=${groupId}, prevEpoch=${prevEpoch}, newEpoch=${newEpoch}, members=${memberAids.length}`);
|
|
1138
1228
|
const gs = generateGroupSecret();
|
|
1139
1229
|
const commitment = computeMembershipCommitment(memberAids, newEpoch, groupId, gs);
|
|
1140
1230
|
const prevChain = current?.epoch_chain ?? null;
|
|
1141
1231
|
const epochChain = computeEpochChain(prevChain, newEpoch, commitment, aid);
|
|
1142
1232
|
const stored = storeGroupSecret(this._keystore, aid, groupId, newEpoch, gs, commitment, memberAids, epochChain);
|
|
1143
1233
|
if (!stored) {
|
|
1234
|
+
this._logger.warn(`rotateEpoch failed (epoch already exists or newer): groupId=${groupId}, newEpoch=${newEpoch}`);
|
|
1144
1235
|
throw new Error(`group ${groupId} epoch ${newEpoch} secret already exists or is newer; abort distribution`);
|
|
1145
1236
|
}
|
|
1146
1237
|
const manifest = this._signManifest(buildMembershipManifest(groupId, newEpoch, prevEpoch, memberAids, { initiatorAid: aid }));
|
|
1147
1238
|
const distPayload = buildKeyDistribution(groupId, newEpoch, gs, memberAids, aid, manifest, epochChain);
|
|
1239
|
+
this._logger.debug(`rotateEpoch success: groupId=${groupId}, newEpoch=${newEpoch}`);
|
|
1148
1240
|
return {
|
|
1149
1241
|
epoch: newEpoch,
|
|
1150
1242
|
commitment,
|
|
@@ -1156,6 +1248,7 @@ export class GroupE2EEManager {
|
|
|
1156
1248
|
/** 指定目标 epoch 号轮换(配合服务端 CAS 使用) */
|
|
1157
1249
|
rotateEpochTo(groupId, newEpoch, memberAids, opts) {
|
|
1158
1250
|
const aid = this._currentAid();
|
|
1251
|
+
this._logger.debug(`rotateEpochTo start: groupId=${groupId}, newEpoch=${newEpoch}, members=${memberAids.length}`);
|
|
1159
1252
|
const current = loadGroupSecret(this._keystore, aid, groupId, newEpoch - 1)
|
|
1160
1253
|
?? loadGroupSecret(this._keystore, aid, groupId);
|
|
1161
1254
|
const gs = generateGroupSecret();
|
|
@@ -1168,6 +1261,7 @@ export class GroupE2EEManager {
|
|
|
1168
1261
|
const rotationId = opts?.rotationId ?? '';
|
|
1169
1262
|
const stored = storeGroupSecret(this._keystore, aid, groupId, newEpoch, gs, commitment, memberAids, epochChain, rotationId);
|
|
1170
1263
|
if (!stored) {
|
|
1264
|
+
this._logger.warn(`rotateEpochTo failed (epoch already exists or newer): groupId=${groupId}, newEpoch=${newEpoch}`);
|
|
1171
1265
|
throw new Error(`group ${groupId} epoch ${newEpoch} secret already exists or is newer; abort distribution`);
|
|
1172
1266
|
}
|
|
1173
1267
|
const manifest = this._signManifest(buildMembershipManifest(groupId, newEpoch, newEpoch - 1, memberAids, { initiatorAid: aid }));
|
|
@@ -1175,6 +1269,7 @@ export class GroupE2EEManager {
|
|
|
1175
1269
|
if (rotationId) {
|
|
1176
1270
|
distPayload.rotation_id = rotationId;
|
|
1177
1271
|
}
|
|
1272
|
+
this._logger.debug(`rotateEpochTo success: groupId=${groupId}, newEpoch=${newEpoch}, rotationId=${rotationId}`);
|
|
1178
1273
|
return {
|
|
1179
1274
|
epoch: newEpoch,
|
|
1180
1275
|
commitment,
|
|
@@ -1189,8 +1284,10 @@ export class GroupE2EEManager {
|
|
|
1189
1284
|
/** 加密群消息(含发送方签名)。无密钥时抛 E2EEGroupSecretMissingError。 */
|
|
1190
1285
|
encrypt(groupId, payload, opts) {
|
|
1191
1286
|
const aid = this._currentAid();
|
|
1287
|
+
this._logger.debug(`group message encrypt start: groupId=${groupId}, aid=${aid}`);
|
|
1192
1288
|
const secretData = loadGroupSecret(this._keystore, aid, groupId);
|
|
1193
1289
|
if (!secretData) {
|
|
1290
|
+
this._logger.error(`group message encrypt failed: missing group secret, groupId=${groupId}`);
|
|
1194
1291
|
throw new E2EEGroupSecretMissingError(`no group secret for ${groupId}`);
|
|
1195
1292
|
}
|
|
1196
1293
|
const identity = this._identityFn();
|
|
@@ -1200,7 +1297,7 @@ export class GroupE2EEManager {
|
|
|
1200
1297
|
throw new E2EEError('sender identity private key unavailable for group message signing');
|
|
1201
1298
|
}
|
|
1202
1299
|
const senderCertPem = identity ? identity.cert ?? null : null;
|
|
1203
|
-
|
|
1300
|
+
const result = encryptGroupMessage(groupId, secretData.epoch, secretData.secret, payload, {
|
|
1204
1301
|
fromAid: aid,
|
|
1205
1302
|
messageId: opts?.messageId ?? `gm-${crypto.randomUUID()}`,
|
|
1206
1303
|
timestamp: opts?.timestamp ?? Date.now(),
|
|
@@ -1209,6 +1306,8 @@ export class GroupE2EEManager {
|
|
|
1209
1306
|
protectedHeaders: opts?.protectedHeaders ?? opts?.protected_headers ?? opts?.headers,
|
|
1210
1307
|
context: opts?.context ?? null,
|
|
1211
1308
|
});
|
|
1309
|
+
this._logger.debug(`group message encrypt success: groupId=${groupId}, epoch=${secretData.epoch}`);
|
|
1310
|
+
return result;
|
|
1212
1311
|
}
|
|
1213
1312
|
/** 使用指定 epoch 加密群消息。 */
|
|
1214
1313
|
encryptWithEpoch(groupId, epoch, payload, opts) {
|
|
@@ -1241,12 +1340,14 @@ export class GroupE2EEManager {
|
|
|
1241
1340
|
}
|
|
1242
1341
|
const groupId = message.group_id || '';
|
|
1243
1342
|
const sender = message.from || message.sender_aid || '';
|
|
1343
|
+
this._logger.debug(`group message decrypt start: groupId=${groupId}, from=${sender}`);
|
|
1244
1344
|
// 防重放预检:优先使用 AAD 内 message_id
|
|
1245
1345
|
const aad = isJsonObject(payload.aad) ? payload.aad : undefined;
|
|
1246
1346
|
const aadMsgId = aad ? (aad.message_id ?? '') : '';
|
|
1247
1347
|
const msgId = aadMsgId || message.message_id || '';
|
|
1248
1348
|
if (!opts?.skipReplay && groupId && sender && msgId) {
|
|
1249
1349
|
if (this._replayGuard.isSeen(groupId, sender, msgId)) {
|
|
1350
|
+
this._logger.debug(`group message replay blocked: groupId=${groupId}, from=${sender}, mid=${msgId}`);
|
|
1250
1351
|
// 返回原消息(不含 e2ee 字段),调用方可通过缺失 e2ee 识别 replay
|
|
1251
1352
|
return message;
|
|
1252
1353
|
}
|
|
@@ -1256,18 +1357,26 @@ export class GroupE2EEManager {
|
|
|
1256
1357
|
if (this._senderCertResolver && sender) {
|
|
1257
1358
|
senderCertPem = this._senderCertResolver(sender);
|
|
1258
1359
|
}
|
|
1259
|
-
if (!senderCertPem)
|
|
1360
|
+
if (!senderCertPem) {
|
|
1361
|
+
this._logger.warn(`group message decrypt rejected: sender cert unavailable, groupId=${groupId}, from=${sender}`);
|
|
1260
1362
|
return null;
|
|
1363
|
+
}
|
|
1261
1364
|
const allSecrets = loadAllGroupSecrets(this._keystore, this._currentAid(), groupId);
|
|
1262
|
-
if (allSecrets.size === 0)
|
|
1365
|
+
if (allSecrets.size === 0) {
|
|
1366
|
+
this._logger.warn(`group message decrypt failed: no group secret, groupId=${groupId}`);
|
|
1263
1367
|
return null;
|
|
1264
|
-
|
|
1368
|
+
}
|
|
1369
|
+
const result = decryptGroupMessage(message, allSecrets, senderCertPem, { logger: this._logger });
|
|
1265
1370
|
// 解密成功后记录防重放
|
|
1266
1371
|
if (result != null) {
|
|
1267
1372
|
const finalMsgId = aadMsgId || message.message_id || '';
|
|
1268
1373
|
if (!opts?.skipReplay && groupId && sender && finalMsgId) {
|
|
1269
1374
|
this._replayGuard.record(groupId, sender, finalMsgId);
|
|
1270
1375
|
}
|
|
1376
|
+
this._logger.debug(`group message decrypt success: groupId=${groupId}, from=${sender}, mid=${msgId}`);
|
|
1377
|
+
}
|
|
1378
|
+
else {
|
|
1379
|
+
this._logger.warn(`group message decrypt failed: groupId=${groupId}, from=${sender}, mid=${msgId}`);
|
|
1271
1380
|
}
|
|
1272
1381
|
return result;
|
|
1273
1382
|
}
|
|
@@ -1286,19 +1395,33 @@ export class GroupE2EEManager {
|
|
|
1286
1395
|
const msgType = payload.type || '';
|
|
1287
1396
|
const aid = this._currentAid();
|
|
1288
1397
|
if (msgType === 'e2ee.group_key_distribution') {
|
|
1289
|
-
|
|
1398
|
+
const groupId = payload.group_id || '';
|
|
1399
|
+
const epoch = payload.epoch;
|
|
1290
1400
|
const distributedBy = payload.distributed_by || '';
|
|
1401
|
+
this._logger.debug(`received key distribution: groupId=${groupId}, epoch=${epoch}, from=${distributedBy}`);
|
|
1402
|
+
let initiatorCert = null;
|
|
1291
1403
|
if (this._initiatorCertResolver && distributedBy) {
|
|
1292
1404
|
initiatorCert = this._initiatorCertResolver(distributedBy);
|
|
1293
1405
|
}
|
|
1294
1406
|
const ok = handleKeyDistribution(payload, this._keystore, aid, initiatorCert, this._logger);
|
|
1407
|
+
if (ok) {
|
|
1408
|
+
this._logger.debug(`key distribution handled successfully: groupId=${groupId}, epoch=${epoch}`);
|
|
1409
|
+
}
|
|
1410
|
+
else {
|
|
1411
|
+
this._logger.warn(`key distribution rejected: groupId=${groupId}, epoch=${epoch}, from=${distributedBy}`);
|
|
1412
|
+
}
|
|
1295
1413
|
return ok ? 'distribution' : 'distribution_rejected';
|
|
1296
1414
|
}
|
|
1297
1415
|
if (msgType === 'e2ee.group_key_response') {
|
|
1416
|
+
const groupId = payload.group_id || '';
|
|
1417
|
+
const epoch = payload.epoch;
|
|
1418
|
+
this._logger.debug(`received key response: groupId=${groupId}, epoch=${epoch}`);
|
|
1298
1419
|
const pendingKey = `${String(payload.group_id ?? '')}:${String(payload.epoch ?? '')}:${String(payload.request_id ?? '')}`;
|
|
1299
1420
|
const expected = this._pendingKeyRequests.get(pendingKey) ?? null;
|
|
1300
|
-
if (expected === null)
|
|
1421
|
+
if (expected === null) {
|
|
1422
|
+
this._logger.warn(`key response rejected (no matching request): groupId=${groupId}, epoch=${epoch}`);
|
|
1301
1423
|
return 'response_rejected';
|
|
1424
|
+
}
|
|
1302
1425
|
const responderAid = String(payload.responder_aid ?? '');
|
|
1303
1426
|
const responderCertPem = responderAid && this._initiatorCertResolver
|
|
1304
1427
|
? this._initiatorCertResolver(responderAid)
|
|
@@ -1312,9 +1435,18 @@ export class GroupE2EEManager {
|
|
|
1312
1435
|
});
|
|
1313
1436
|
if (ok && expected)
|
|
1314
1437
|
this._pendingKeyRequests.delete(pendingKey);
|
|
1438
|
+
if (ok) {
|
|
1439
|
+
this._logger.debug(`key response handled successfully: groupId=${groupId}, epoch=${epoch}, responder=${responderAid}`);
|
|
1440
|
+
}
|
|
1441
|
+
else {
|
|
1442
|
+
this._logger.warn(`key response verification failed: groupId=${groupId}, epoch=${epoch}, responder=${responderAid}`);
|
|
1443
|
+
}
|
|
1315
1444
|
return ok ? 'response' : 'response_rejected';
|
|
1316
1445
|
}
|
|
1317
1446
|
if (msgType === 'e2ee.group_key_request') {
|
|
1447
|
+
const groupId = payload.group_id || '';
|
|
1448
|
+
const requester = payload.requester_aid || '';
|
|
1449
|
+
this._logger.debug(`received key request: groupId=${groupId}, requester=${requester}, epoch=${payload.epoch}`);
|
|
1318
1450
|
return 'request';
|
|
1319
1451
|
}
|
|
1320
1452
|
return null;
|
|
@@ -1354,16 +1486,28 @@ export class GroupE2EEManager {
|
|
|
1354
1486
|
if (!requester || !groupId)
|
|
1355
1487
|
return null;
|
|
1356
1488
|
// 成员资格验证
|
|
1357
|
-
if (!members.includes(requester))
|
|
1489
|
+
if (!members.includes(requester)) {
|
|
1490
|
+
this._logger.warn(`key request rejected (not a member): groupId=${groupId}, requester=${requester}`);
|
|
1358
1491
|
return null;
|
|
1492
|
+
}
|
|
1359
1493
|
if (!this._responseThrottle.allow(`response:${groupId}:${requester}`)) {
|
|
1494
|
+
this._logger.debug(`key request throttled: groupId=${groupId}, requester=${requester}`);
|
|
1360
1495
|
return null;
|
|
1361
1496
|
}
|
|
1362
1497
|
const identity = this._identityFn();
|
|
1363
1498
|
const privateKeyPem = identity.private_key_pem;
|
|
1364
|
-
if (!privateKeyPem)
|
|
1499
|
+
if (!privateKeyPem) {
|
|
1500
|
+
this._logger.error(`key request handling failed: missing private key, groupId=${groupId}`);
|
|
1365
1501
|
return null;
|
|
1366
|
-
|
|
1502
|
+
}
|
|
1503
|
+
const response = handleKeyRequest(requestPayload, this._keystore, this._currentAid(), members, privateKeyPem, this._logger);
|
|
1504
|
+
if (response) {
|
|
1505
|
+
this._logger.debug(`key request responded: groupId=${groupId}, requester=${requester}, epoch=${requestPayload.epoch}`);
|
|
1506
|
+
}
|
|
1507
|
+
else {
|
|
1508
|
+
this._logger.warn(`key request handling failed (no local key or requester not in epoch members): groupId=${groupId}, requester=${requester}`);
|
|
1509
|
+
}
|
|
1510
|
+
return response;
|
|
1367
1511
|
}
|
|
1368
1512
|
// ── 状态查询 ──────────────────────────────────────────
|
|
1369
1513
|
/** 检查是否有群组密钥 */
|
|
@@ -1382,7 +1526,14 @@ export class GroupE2EEManager {
|
|
|
1382
1526
|
}
|
|
1383
1527
|
/** 加载群组密钥数据 */
|
|
1384
1528
|
loadSecret(groupId, epoch) {
|
|
1385
|
-
|
|
1529
|
+
const result = loadGroupSecret(this._keystore, this._currentAid(), groupId, epoch);
|
|
1530
|
+
if (result) {
|
|
1531
|
+
this._logger.debug(`load group secret success: groupId=${groupId}, epoch=${result.epoch}`);
|
|
1532
|
+
}
|
|
1533
|
+
else {
|
|
1534
|
+
this._logger.debug(`load group secret not found: groupId=${groupId}, requestedEpoch=${epoch ?? 'latest'}`);
|
|
1535
|
+
}
|
|
1536
|
+
return result;
|
|
1386
1537
|
}
|
|
1387
1538
|
/** 清理过期的旧 epoch */
|
|
1388
1539
|
cleanup(groupId, retentionSeconds = OLD_EPOCH_RETENTION_SECONDS) {
|