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