@agentunion/fastaun 0.2.15 → 0.2.17
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 +28 -25
- package/dist/auth.js.map +1 -1
- package/dist/client.d.ts +48 -7
- package/dist/client.js +1079 -280
- 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/e2ee-group.d.ts +36 -1
- package/dist/e2ee-group.js +134 -23
- package/dist/e2ee-group.js.map +1 -1
- package/dist/e2ee.d.ts +3 -0
- package/dist/e2ee.js +12 -4
- 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 +11 -0
- package/dist/keystore/aid-db.js +49 -2
- package/dist/keystore/aid-db.js.map +1 -1
- package/dist/keystore/file.d.ts +5 -0
- package/dist/keystore/file.js +12 -6
- package/dist/keystore/file.js.map +1 -1
- package/dist/keystore/index.d.ts +14 -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 +26 -3
- package/dist/logger.js +117 -40
- package/dist/logger.js.map +1 -1
- package/dist/secret-store/file-store.d.ts +4 -0
- package/dist/secret-store/file-store.js +5 -2
- 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 +9 -1
- package/dist/transport.js.map +1 -1
- package/package.json +1 -1
package/dist/config.d.ts
CHANGED
package/dist/config.js
CHANGED
|
@@ -93,6 +93,7 @@ export function defaultConfig() {
|
|
|
93
93
|
verifySsl: resolveVerifySslFromEnv(),
|
|
94
94
|
requireForwardSecrecy: true,
|
|
95
95
|
replayWindowSeconds: 300,
|
|
96
|
+
debug: false,
|
|
96
97
|
};
|
|
97
98
|
}
|
|
98
99
|
// ── 从字典构建配置 ───────────────────────────────────────────
|
|
@@ -114,6 +115,7 @@ export function configFromMap(raw) {
|
|
|
114
115
|
verifySsl: readBoolean(raw.verify_ssl ?? raw.verifySSL ?? raw.verifySsl, def.verifySsl),
|
|
115
116
|
requireForwardSecrecy: readBoolean(raw.require_forward_secrecy ?? raw.requireForwardSecrecy, def.requireForwardSecrecy),
|
|
116
117
|
replayWindowSeconds: readOptionalNumber(raw.replay_window_seconds ?? raw.replayWindowSeconds, def.replayWindowSeconds) ?? def.replayWindowSeconds,
|
|
118
|
+
debug: readBoolean(raw.debug, def.debug),
|
|
117
119
|
};
|
|
118
120
|
}
|
|
119
121
|
//# sourceMappingURL=config.js.map
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACxF,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAG9C,8DAA8D;AAE9D,MAAM,mBAAmB,GAAG,yBAAyB,CAAC;AACtD,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,aAAa,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC;AAEhE,MAAM,UAAU,mBAAmB,CACjC,KAAc,EACd,KAAa,EACb,OAAiC,EAAE;IAEnC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACxC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,IAAI,IAAI,CAAC,UAAU;YAAE,OAAO,EAAE,CAAC;QAC/B,MAAM,IAAI,eAAe,CAAC,GAAG,KAAK,6BAA6B,CAAC,CAAC;IACnE,CAAC;IACD,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,eAAe,CAAC,GAAG,KAAK,kCAAkC,CAAC,CAAC;IACxE,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,OAAgB;IAC1C,MAAM,IAAI,GAAG,OAAO,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,MAAM,CAAC,CAAC;IAChD,SAAS,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACrC,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IAE9C,IAAI,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QAC7B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;YAC1D,IAAI,MAAM;gBAAE,OAAO,mBAAmB,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;QAC9D,CAAC;QAAC,MAAM,CAAC;YACP,gBAAgB;QAClB,CAAC;IACH,CAAC;IAED,MAAM,KAAK,GAAG,mBAAmB,CAAC,UAAU,EAAE,EAAE,WAAW,CAAC,CAAC;IAC7D,IAAI,CAAC;QACH,aAAa,CAAC,YAAY,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;QAC5C,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;YACjC,IAAI,CAAC;gBACH,SAAS,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;YACjC,CAAC;YAAC,MAAM,CAAC;gBACP,gBAAgB;YAClB,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,gBAAgB;IAClB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/e2ee-group.d.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import type { KeyStore } from './keystore/index.js';
|
|
8
8
|
import type { ProtectedHeadersInput } from './e2ee.js';
|
|
9
9
|
import { type IdentityRecord, type JsonObject, type Message } from './types.js';
|
|
10
|
+
import type { ModuleLogger } from './logger.js';
|
|
10
11
|
export interface LoadedGroupSecret {
|
|
11
12
|
epoch: number;
|
|
12
13
|
secret: Buffer;
|
|
@@ -17,6 +18,20 @@ export interface LoadedGroupSecret {
|
|
|
17
18
|
epoch_chain_unverified?: boolean;
|
|
18
19
|
epoch_chain_unverified_reason?: string;
|
|
19
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* ECIES 加密:P-256 ECDH + HKDF-SHA256 + AES-256-GCM。
|
|
23
|
+
* @param peerPubkeyBytes 65 字节未压缩 P-256 公钥 (0x04 开头)
|
|
24
|
+
* @param plaintext 待加密明文
|
|
25
|
+
* @returns ephemeral_pubkey(65B) || iv(12B) || ciphertext || tag(16B)
|
|
26
|
+
*/
|
|
27
|
+
export declare function eciesEncrypt(peerPubkeyBytes: Buffer, plaintext: Buffer): Buffer;
|
|
28
|
+
/**
|
|
29
|
+
* ECIES 解密:对应 eciesEncrypt。
|
|
30
|
+
* @param privateKeyPem 自己的 PEM 格式 EC 私钥
|
|
31
|
+
* @param ciphertext ephemeral_pubkey(65B) || iv(12B) || encrypted || tag(16B)
|
|
32
|
+
* @returns 解密后的明文
|
|
33
|
+
*/
|
|
34
|
+
export declare function eciesDecrypt(privateKeyPem: string, ciphertext: Buffer): Buffer;
|
|
20
35
|
/**
|
|
21
36
|
* 计算 Epoch Transcript Chain。
|
|
22
37
|
* genesis(prev_chain=null)使用固定前缀;后续 epoch 使用上一个 chain 的字节。
|
|
@@ -62,6 +77,22 @@ export declare function computeMembershipCommitment(memberAids: string[], epoch:
|
|
|
62
77
|
* 2. 检查 myAid 是否在 memberAids 中
|
|
63
78
|
*/
|
|
64
79
|
export declare function verifyMembershipCommitment(commitment: string, memberAids: string[], epoch: number, groupId: string, myAid: string, groupSecret: Buffer): boolean;
|
|
80
|
+
/**
|
|
81
|
+
* 计算群组状态哈希(链式)。
|
|
82
|
+
* state_hash = SHA-256(group_id | 0x00 | state_version(u64be) | 0x00 |
|
|
83
|
+
* key_epoch(u64be) | 0x00 | membership_block | 0x00 | policy_block | 0x00 | prev_state_hash(32B))
|
|
84
|
+
*/
|
|
85
|
+
export declare function computeStateHash(params: {
|
|
86
|
+
groupId: string;
|
|
87
|
+
stateVersion: number;
|
|
88
|
+
keyEpoch: number;
|
|
89
|
+
members: Array<{
|
|
90
|
+
aid: string;
|
|
91
|
+
role: string;
|
|
92
|
+
}>;
|
|
93
|
+
policy: Record<string, unknown>;
|
|
94
|
+
prevStateHash: string;
|
|
95
|
+
}): string;
|
|
65
96
|
/** 构建 Membership Manifest(未签名) */
|
|
66
97
|
export declare function buildMembershipManifest(groupId: string, epoch: number, prevEpoch: number | null, memberAids: string[], opts?: {
|
|
67
98
|
added?: string[];
|
|
@@ -115,7 +146,7 @@ export declare function generateGroupSecret(): Buffer;
|
|
|
115
146
|
/** 构建 group key 分发消息 payload */
|
|
116
147
|
export declare function buildKeyDistribution(groupId: string, epoch: number, groupSecret: Buffer, memberAids: string[], distributedBy: string, manifest?: JsonObject | null, epochChain?: string): JsonObject;
|
|
117
148
|
/** 处理收到的 group key 分发消息 */
|
|
118
|
-
export declare function handleKeyDistribution(message: Message | JsonObject, keystore: KeyStore, aid: string, initiatorCertPem?: string | null): boolean;
|
|
149
|
+
export declare function handleKeyDistribution(message: Message | JsonObject, keystore: KeyStore, aid: string, initiatorCertPem?: string | null, logger?: ModuleLogger): boolean;
|
|
119
150
|
/** 构建密钥请求 payload */
|
|
120
151
|
export declare function buildKeyRequest(groupId: string, epoch: number, requesterAid: string, requestId?: string): JsonObject;
|
|
121
152
|
/** 处理收到的密钥请求 */
|
|
@@ -126,6 +157,7 @@ export declare function handleKeyResponse(response: Message | JsonObject, keysto
|
|
|
126
157
|
responderCertPem?: string | null;
|
|
127
158
|
currentMembers?: string[];
|
|
128
159
|
strict?: boolean;
|
|
160
|
+
logger?: ModuleLogger;
|
|
129
161
|
}): boolean;
|
|
130
162
|
export declare class GroupE2EEManager {
|
|
131
163
|
private _identityFn;
|
|
@@ -136,6 +168,7 @@ export declare class GroupE2EEManager {
|
|
|
136
168
|
private _senderCertResolver;
|
|
137
169
|
private _initiatorCertResolver;
|
|
138
170
|
private _pendingKeyRequests;
|
|
171
|
+
private _logger;
|
|
139
172
|
constructor(opts: {
|
|
140
173
|
identityFn: () => IdentityRecord;
|
|
141
174
|
keystore: KeyStore;
|
|
@@ -143,6 +176,7 @@ export declare class GroupE2EEManager {
|
|
|
143
176
|
responseCooldown?: number;
|
|
144
177
|
senderCertResolver?: (aid: string) => string | null;
|
|
145
178
|
initiatorCertResolver?: (aid: string) => string | null;
|
|
179
|
+
logger?: ModuleLogger;
|
|
146
180
|
});
|
|
147
181
|
/** 用当前身份私钥签名 manifest */
|
|
148
182
|
private _signManifest;
|
|
@@ -153,6 +187,7 @@ export declare class GroupE2EEManager {
|
|
|
153
187
|
/** 指定目标 epoch 号轮换(配合服务端 CAS 使用) */
|
|
154
188
|
rotateEpochTo(groupId: string, newEpoch: number, memberAids: string[], opts?: {
|
|
155
189
|
rotationId?: string;
|
|
190
|
+
prevChainHint?: string | null;
|
|
156
191
|
}): JsonObject;
|
|
157
192
|
discardPendingSecret(groupId: string, epoch: number, rotationId: string): boolean;
|
|
158
193
|
/** 加密群消息(含发送方签名)。无密钥时抛 E2EEGroupSecretMissingError。 */
|
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) {
|
|
@@ -38,6 +39,61 @@ const PROTECTED_HEADERS_DOMAIN = Buffer.from('aun-protected-headers-v1', 'utf-8'
|
|
|
38
39
|
const PROTECTED_CONTEXT_DOMAIN = Buffer.from('aun-protected-context-v1', 'utf-8');
|
|
39
40
|
/** 旧 epoch 默认保留 7 天 */
|
|
40
41
|
const OLD_EPOCH_RETENTION_SECONDS = 7 * 24 * 3600;
|
|
42
|
+
// ── ECIES (P-256 ECDH + HKDF-SHA256 + AES-256-GCM) ──────────
|
|
43
|
+
const ECIES_HKDF_INFO = Buffer.from('aun-epoch-key-ecies', 'utf-8');
|
|
44
|
+
/**
|
|
45
|
+
* ECIES 加密:P-256 ECDH + HKDF-SHA256 + AES-256-GCM。
|
|
46
|
+
* @param peerPubkeyBytes 65 字节未压缩 P-256 公钥 (0x04 开头)
|
|
47
|
+
* @param plaintext 待加密明文
|
|
48
|
+
* @returns ephemeral_pubkey(65B) || iv(12B) || ciphertext || tag(16B)
|
|
49
|
+
*/
|
|
50
|
+
export function eciesEncrypt(peerPubkeyBytes, plaintext) {
|
|
51
|
+
// 生成临时密钥对
|
|
52
|
+
const ephemeral = crypto.createECDH('prime256v1');
|
|
53
|
+
ephemeral.generateKeys();
|
|
54
|
+
const ephemeralPubBytes = ephemeral.getPublicKey(); // 65 字节未压缩
|
|
55
|
+
// ECDH 共享密钥
|
|
56
|
+
const shared = ephemeral.computeSecret(peerPubkeyBytes);
|
|
57
|
+
// HKDF 派生 32 字节 AES 密钥
|
|
58
|
+
const derived = crypto.hkdfSync('sha256', shared, Buffer.alloc(0), ECIES_HKDF_INFO, 32);
|
|
59
|
+
const aesKey = Buffer.from(derived);
|
|
60
|
+
// AES-256-GCM 加密
|
|
61
|
+
const iv = crypto.randomBytes(12);
|
|
62
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', aesKey, iv);
|
|
63
|
+
const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
64
|
+
const tag = cipher.getAuthTag(); // 16 字节
|
|
65
|
+
return Buffer.concat([ephemeralPubBytes, iv, encrypted, tag]);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* ECIES 解密:对应 eciesEncrypt。
|
|
69
|
+
* @param privateKeyPem 自己的 PEM 格式 EC 私钥
|
|
70
|
+
* @param ciphertext ephemeral_pubkey(65B) || iv(12B) || encrypted || tag(16B)
|
|
71
|
+
* @returns 解密后的明文
|
|
72
|
+
*/
|
|
73
|
+
export function eciesDecrypt(privateKeyPem, ciphertext) {
|
|
74
|
+
if (ciphertext.length < 65 + 12 + 16) {
|
|
75
|
+
throw new E2EEError('ECIES ciphertext too short');
|
|
76
|
+
}
|
|
77
|
+
const ephemeralPubBytes = ciphertext.subarray(0, 65);
|
|
78
|
+
const iv = ciphertext.subarray(65, 77);
|
|
79
|
+
const encryptedWithTag = ciphertext.subarray(77);
|
|
80
|
+
const tag = encryptedWithTag.subarray(encryptedWithTag.length - 16);
|
|
81
|
+
const encrypted = encryptedWithTag.subarray(0, encryptedWithTag.length - 16);
|
|
82
|
+
// 从 PEM 私钥创建 ECDH 并计算共享密钥
|
|
83
|
+
const privKeyObj = crypto.createPrivateKey(privateKeyPem);
|
|
84
|
+
const ecdh = crypto.createECDH('prime256v1');
|
|
85
|
+
// 从 JWK 提取私钥 d 值设置到 ECDH
|
|
86
|
+
const jwk = privKeyObj.export({ format: 'jwk' });
|
|
87
|
+
ecdh.setPrivateKey(Buffer.from(jwk.d, 'base64url'));
|
|
88
|
+
const shared = ecdh.computeSecret(ephemeralPubBytes);
|
|
89
|
+
// HKDF 派生 AES 密钥
|
|
90
|
+
const derived = crypto.hkdfSync('sha256', shared, Buffer.alloc(0), ECIES_HKDF_INFO, 32);
|
|
91
|
+
const aesKey = Buffer.from(derived);
|
|
92
|
+
// AES-256-GCM 解密
|
|
93
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', aesKey, iv);
|
|
94
|
+
decipher.setAuthTag(tag);
|
|
95
|
+
return Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
96
|
+
}
|
|
41
97
|
// ── Epoch Transcript Chain ────────────────────────────────────
|
|
42
98
|
const EPOCH_CHAIN_GENESIS_PREFIX = Buffer.from('aun-epoch-chain:genesis', 'utf-8');
|
|
43
99
|
/**
|
|
@@ -545,6 +601,42 @@ export function verifyMembershipCommitment(commitment, memberAids, epoch, groupI
|
|
|
545
601
|
return false;
|
|
546
602
|
return crypto.timingSafeEqual(a, b);
|
|
547
603
|
}
|
|
604
|
+
// ── State Hash ────────────────────────────────────────────────
|
|
605
|
+
/**
|
|
606
|
+
* 计算群组状态哈希(链式)。
|
|
607
|
+
* state_hash = SHA-256(group_id | 0x00 | state_version(u64be) | 0x00 |
|
|
608
|
+
* key_epoch(u64be) | 0x00 | membership_block | 0x00 | policy_block | 0x00 | prev_state_hash(32B))
|
|
609
|
+
*/
|
|
610
|
+
export function computeStateHash(params) {
|
|
611
|
+
const { groupId, stateVersion, keyEpoch, members, policy, prevStateHash } = params;
|
|
612
|
+
// 按 AID 排序,构建 membership_block
|
|
613
|
+
const sorted = [...members].sort((a, b) => a.aid.localeCompare(b.aid));
|
|
614
|
+
const membershipBlock = sorted.map(m => `${m.aid}:${m.role}`).join('|');
|
|
615
|
+
// 按 key 排序的 canonical JSON(无空格)
|
|
616
|
+
const sortedPolicy = {};
|
|
617
|
+
for (const key of Object.keys(policy).sort()) {
|
|
618
|
+
sortedPolicy[key] = policy[key];
|
|
619
|
+
}
|
|
620
|
+
const policyBlock = Object.keys(policy).length > 0 ? JSON.stringify(sortedPolicy) : '';
|
|
621
|
+
// prev_state_hash: 32 字节,空则全零
|
|
622
|
+
const prevBytes = prevStateHash
|
|
623
|
+
? Buffer.from(prevStateHash, 'hex')
|
|
624
|
+
: Buffer.alloc(32);
|
|
625
|
+
// state_version / key_epoch → uint64 big-endian
|
|
626
|
+
const svBuf = Buffer.alloc(8);
|
|
627
|
+
svBuf.writeBigUInt64BE(BigInt(stateVersion));
|
|
628
|
+
const keBuf = Buffer.alloc(8);
|
|
629
|
+
keBuf.writeBigUInt64BE(BigInt(keyEpoch));
|
|
630
|
+
const data = Buffer.concat([
|
|
631
|
+
Buffer.from(groupId, 'utf-8'), Buffer.from([0x00]),
|
|
632
|
+
svBuf, Buffer.from([0x00]),
|
|
633
|
+
keBuf, Buffer.from([0x00]),
|
|
634
|
+
Buffer.from(membershipBlock, 'utf-8'), Buffer.from([0x00]),
|
|
635
|
+
Buffer.from(policyBlock, 'utf-8'), Buffer.from([0x00]),
|
|
636
|
+
prevBytes,
|
|
637
|
+
]);
|
|
638
|
+
return crypto.createHash('sha256').update(data).digest('hex');
|
|
639
|
+
}
|
|
548
640
|
// ── Membership Manifest ───────────────────────────────────────
|
|
549
641
|
/** 构建 Membership Manifest(未签名) */
|
|
550
642
|
export function buildMembershipManifest(groupId, epoch, prevEpoch, memberAids, opts) {
|
|
@@ -656,12 +748,12 @@ export function loadGroupSecret(keystore, aid, groupId, epoch) {
|
|
|
656
748
|
}
|
|
657
749
|
return loaded;
|
|
658
750
|
}
|
|
659
|
-
function assessIncomingEpochChain(keystore, aid, groupId, epoch, commitment, incomingChain, rotationId, rotatorAid, source) {
|
|
751
|
+
function assessIncomingEpochChain(keystore, aid, groupId, epoch, commitment, incomingChain, rotationId, rotatorAid, source, logger = _noopLogger) {
|
|
660
752
|
const chain = (incomingChain ?? '').trim();
|
|
661
753
|
const rid = rotationId.trim();
|
|
662
754
|
const rotator = rotatorAid.trim();
|
|
663
755
|
if (rid && !chain) {
|
|
664
|
-
|
|
756
|
+
logger.warn(`[e2ee-group] 拒绝缺少 epoch_chain 的新 rotation key source=${source} group=${groupId} epoch=${epoch} rotation=${rid}`);
|
|
665
757
|
return { ok: false };
|
|
666
758
|
}
|
|
667
759
|
const current = loadGroupSecret(keystore, aid, groupId);
|
|
@@ -672,7 +764,7 @@ function assessIncomingEpochChain(keystore, aid, groupId, epoch, commitment, inc
|
|
|
672
764
|
return { ok: true };
|
|
673
765
|
if (rid && chain && currentChain && currentChain !== chain) {
|
|
674
766
|
if (!(currentPendingRotationId && currentPendingRotationId !== rid)) {
|
|
675
|
-
|
|
767
|
+
logger.warn(`[e2ee-group] 拒绝同 epoch 分叉 chain source=${source} group=${groupId} epoch=${epoch} rotation=${rid}`);
|
|
676
768
|
return { ok: false };
|
|
677
769
|
}
|
|
678
770
|
}
|
|
@@ -685,17 +777,17 @@ function assessIncomingEpochChain(keystore, aid, groupId, epoch, commitment, inc
|
|
|
685
777
|
return { ok: true, unverified: true, reason: 'missing_prev_chain' };
|
|
686
778
|
if (!rotator) {
|
|
687
779
|
if (rid) {
|
|
688
|
-
|
|
780
|
+
logger.warn(`[e2ee-group] 拒绝缺少 rotator_aid 的新 rotation key source=${source} group=${groupId} epoch=${epoch} rotation=${rid}`);
|
|
689
781
|
return { ok: false };
|
|
690
782
|
}
|
|
691
783
|
return { ok: true, unverified: true, reason: 'missing_rotator_aid' };
|
|
692
784
|
}
|
|
693
785
|
if (!verifyEpochChain(chain, prevChain, epoch, commitment, rotator)) {
|
|
694
786
|
if (rid) {
|
|
695
|
-
|
|
787
|
+
logger.warn(`[e2ee-group] 拒绝 epoch_chain 验证失败的新 rotation key source=${source} group=${groupId} epoch=${epoch} rotation=${rid}`);
|
|
696
788
|
return { ok: false };
|
|
697
789
|
}
|
|
698
|
-
|
|
790
|
+
logger.warn(`[e2ee-group] epoch_chain 验证失败,按兼容档接收并标记未验证 source=${source} group=${groupId} epoch=${epoch}`);
|
|
699
791
|
return { ok: true, unverified: true, reason: 'chain_mismatch_legacy' };
|
|
700
792
|
}
|
|
701
793
|
if (!rid)
|
|
@@ -823,7 +915,7 @@ export function buildKeyDistribution(groupId, epoch, groupSecret, memberAids, di
|
|
|
823
915
|
return result;
|
|
824
916
|
}
|
|
825
917
|
/** 处理收到的 group key 分发消息 */
|
|
826
|
-
export function handleKeyDistribution(message, keystore, aid, initiatorCertPem) {
|
|
918
|
+
export function handleKeyDistribution(message, keystore, aid, initiatorCertPem, logger) {
|
|
827
919
|
const payload = 'group_id' in message
|
|
828
920
|
? message
|
|
829
921
|
: (isJsonObject(message.payload) ? message.payload : message);
|
|
@@ -863,7 +955,7 @@ export function handleKeyDistribution(message, keystore, aid, initiatorCertPem)
|
|
|
863
955
|
}
|
|
864
956
|
const incomingChain = typeof payload.epoch_chain === 'string' ? payload.epoch_chain : undefined;
|
|
865
957
|
const rotationId = typeof payload.rotation_id === 'string' ? payload.rotation_id : '';
|
|
866
|
-
const chainAssessment = assessIncomingEpochChain(keystore, aid, groupId, epoch, commitment, incomingChain, rotationId, String(payload.distributed_by ?? payload.rotator_aid ?? ''), 'key_distribution');
|
|
958
|
+
const chainAssessment = assessIncomingEpochChain(keystore, aid, groupId, epoch, commitment, incomingChain, rotationId, String(payload.distributed_by ?? payload.rotator_aid ?? ''), 'key_distribution', logger);
|
|
867
959
|
if (!chainAssessment.ok)
|
|
868
960
|
return false;
|
|
869
961
|
return storeGroupSecret(keystore, aid, groupId, epoch, groupSecret, commitment, memberAids, incomingChain, rotationId, chainAssessment.unverified, chainAssessment.reason);
|
|
@@ -896,19 +988,20 @@ export function handleKeyRequest(request, keystore, aid, currentMembers, private
|
|
|
896
988
|
const secretData = loadGroupSecret(keystore, aid, groupId, epoch);
|
|
897
989
|
if (!secretData)
|
|
898
990
|
return null;
|
|
899
|
-
|
|
991
|
+
// 历史隔离:密钥存储中记录了该 epoch 的成员列表,
|
|
992
|
+
// 若请求者不在该列表中,说明其不属于该 epoch,直接拒绝(不响应)。
|
|
900
993
|
const memberAids = (secretData.member_aids ?? []).map(String).filter(Boolean).sort();
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
let includeEpochChain = true;
|
|
904
|
-
if (currentMemberAids.includes(requesterAid) && !responseMemberAids.includes(requesterAid)) {
|
|
905
|
-
responseMemberAids = currentMemberAids;
|
|
906
|
-
commitmentStr = computeMembershipCommitment(responseMemberAids, epoch, groupId, secretData.secret);
|
|
907
|
-
includeEpochChain = false;
|
|
908
|
-
}
|
|
909
|
-
else if (!commitmentStr) {
|
|
910
|
-
commitmentStr = computeMembershipCommitment(responseMemberAids, epoch, groupId, secretData.secret);
|
|
994
|
+
if (memberAids.length > 0 && !memberAids.includes(requesterAid)) {
|
|
995
|
+
return null;
|
|
911
996
|
}
|
|
997
|
+
// 确定响应使用的成员列表:优先使用密钥存储中的成员列表,
|
|
998
|
+
// 若为空(旧数据),则降级使用当前成员列表。
|
|
999
|
+
const responseMemberAids = memberAids.length > 0
|
|
1000
|
+
? memberAids
|
|
1001
|
+
: currentMembers.map(String).filter(Boolean).sort();
|
|
1002
|
+
// 若存储中无 commitment,按当前成员列表重新计算
|
|
1003
|
+
const commitmentStr = secretData.commitment
|
|
1004
|
+
|| computeMembershipCommitment(responseMemberAids, epoch, groupId, secretData.secret);
|
|
912
1005
|
const response = {
|
|
913
1006
|
type: 'e2ee.group_key_response',
|
|
914
1007
|
group_id: groupId,
|
|
@@ -921,7 +1014,8 @@ export function handleKeyRequest(request, keystore, aid, currentMembers, private
|
|
|
921
1014
|
responder_aid: aid,
|
|
922
1015
|
issued_at: Date.now(),
|
|
923
1016
|
};
|
|
924
|
-
|
|
1017
|
+
// epoch_chain 始终包含(若有),供接收方验证历史轮转链
|
|
1018
|
+
if (secretData.epoch_chain !== undefined) {
|
|
925
1019
|
response.epoch_chain = secretData.epoch_chain;
|
|
926
1020
|
}
|
|
927
1021
|
return privateKeyPem ? signGroupKeyResponse(response, privateKeyPem) : response;
|
|
@@ -969,9 +1063,20 @@ export function handleKeyResponse(response, keystore, aid, opts) {
|
|
|
969
1063
|
if (!verifyMembershipCommitment(commitment, memberAids, epoch, groupId, aid, groupSecret)) {
|
|
970
1064
|
return false;
|
|
971
1065
|
}
|
|
1066
|
+
const manifest = isJsonObject(payload.manifest) ? payload.manifest : null;
|
|
1067
|
+
if (manifest) {
|
|
1068
|
+
if (manifest.group_id !== groupId || manifest.epoch !== epoch)
|
|
1069
|
+
return false;
|
|
1070
|
+
const manifestMembers = Array.isArray(manifest.member_aids)
|
|
1071
|
+
? manifest.member_aids.map((item) => String(item ?? '').trim()).filter(Boolean).sort()
|
|
1072
|
+
: [];
|
|
1073
|
+
const payloadMembers = memberAids.map((item) => String(item ?? '').trim()).filter(Boolean).sort();
|
|
1074
|
+
if (manifestMembers.length > 0 && manifestMembers.join('\n') !== payloadMembers.join('\n'))
|
|
1075
|
+
return false;
|
|
1076
|
+
}
|
|
972
1077
|
const incomingChain = typeof payload.epoch_chain === 'string' ? payload.epoch_chain : undefined;
|
|
973
1078
|
const rotationId = typeof payload.rotation_id === 'string' ? payload.rotation_id : '';
|
|
974
|
-
const chainAssessment = assessIncomingEpochChain(keystore, aid, groupId, epoch, commitment, incomingChain, rotationId, String(payload.distributed_by ?? payload.rotator_aid ?? payload.responder_aid ?? ''), 'key_response');
|
|
1079
|
+
const chainAssessment = assessIncomingEpochChain(keystore, aid, groupId, epoch, commitment, incomingChain, rotationId, String(payload.distributed_by ?? payload.rotator_aid ?? payload.responder_aid ?? ''), 'key_response', opts?.logger);
|
|
975
1080
|
if (!chainAssessment.ok)
|
|
976
1081
|
return false;
|
|
977
1082
|
return storeGroupSecretEpoch(keystore, aid, groupId, epoch, groupSecret, commitment, memberAids, incomingChain, rotationId, chainAssessment.unverified, chainAssessment.reason);
|
|
@@ -986,6 +1091,7 @@ export class GroupE2EEManager {
|
|
|
986
1091
|
_senderCertResolver;
|
|
987
1092
|
_initiatorCertResolver;
|
|
988
1093
|
_pendingKeyRequests = new Map();
|
|
1094
|
+
_logger;
|
|
989
1095
|
constructor(opts) {
|
|
990
1096
|
this._identityFn = opts.identityFn;
|
|
991
1097
|
this._keystore = opts.keystore;
|
|
@@ -994,6 +1100,7 @@ export class GroupE2EEManager {
|
|
|
994
1100
|
this._responseThrottle = new GroupKeyRequestThrottle(opts.responseCooldown ?? 30);
|
|
995
1101
|
this._senderCertResolver = opts.senderCertResolver ?? null;
|
|
996
1102
|
this._initiatorCertResolver = opts.initiatorCertResolver ?? null;
|
|
1103
|
+
this._logger = opts.logger ?? _noopLogger;
|
|
997
1104
|
}
|
|
998
1105
|
// ── 密钥管理 ──────────────────────────────────────────
|
|
999
1106
|
/** 用当前身份私钥签名 manifest */
|
|
@@ -1053,7 +1160,10 @@ export class GroupE2EEManager {
|
|
|
1053
1160
|
?? loadGroupSecret(this._keystore, aid, groupId);
|
|
1054
1161
|
const gs = generateGroupSecret();
|
|
1055
1162
|
const commitment = computeMembershipCommitment(memberAids, newEpoch, groupId, gs);
|
|
1056
|
-
|
|
1163
|
+
let prevChain = current?.epoch_chain ?? null;
|
|
1164
|
+
if (!prevChain && opts?.prevChainHint) {
|
|
1165
|
+
prevChain = opts.prevChainHint;
|
|
1166
|
+
}
|
|
1057
1167
|
const epochChain = computeEpochChain(prevChain, newEpoch, commitment, aid);
|
|
1058
1168
|
const rotationId = opts?.rotationId ?? '';
|
|
1059
1169
|
const stored = storeGroupSecret(this._keystore, aid, groupId, newEpoch, gs, commitment, memberAids, epochChain, rotationId);
|
|
@@ -1181,7 +1291,7 @@ export class GroupE2EEManager {
|
|
|
1181
1291
|
if (this._initiatorCertResolver && distributedBy) {
|
|
1182
1292
|
initiatorCert = this._initiatorCertResolver(distributedBy);
|
|
1183
1293
|
}
|
|
1184
|
-
const ok = handleKeyDistribution(payload, this._keystore, aid, initiatorCert);
|
|
1294
|
+
const ok = handleKeyDistribution(payload, this._keystore, aid, initiatorCert, this._logger);
|
|
1185
1295
|
return ok ? 'distribution' : 'distribution_rejected';
|
|
1186
1296
|
}
|
|
1187
1297
|
if (msgType === 'e2ee.group_key_response') {
|
|
@@ -1198,6 +1308,7 @@ export class GroupE2EEManager {
|
|
|
1198
1308
|
responderCertPem,
|
|
1199
1309
|
currentMembers: this.getMemberAids(String(payload.group_id ?? '')),
|
|
1200
1310
|
strict: true,
|
|
1311
|
+
logger: this._logger,
|
|
1201
1312
|
});
|
|
1202
1313
|
if (ok && expected)
|
|
1203
1314
|
this._pendingKeyRequests.delete(pendingKey);
|