@agentunion/fastaun-browser 0.4.4 → 0.4.5
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/CHANGELOG.md +13 -0
- package/_packed_docs/CHANGELOG.md +13 -0
- package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +1 -1
- package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +1 -1
- package/dist/aid-store.d.ts +1 -0
- package/dist/aid-store.d.ts.map +1 -1
- package/dist/aid-store.js +26 -9
- package/dist/aid-store.js.map +1 -1
- package/dist/auth.d.ts +8 -13
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +37 -130
- package/dist/auth.js.map +1 -1
- package/dist/bundle.js +644 -210
- package/dist/client.d.ts +5 -4
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +64 -66
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/keystore/index.d.ts +45 -22
- package/dist/keystore/index.d.ts.map +1 -1
- package/dist/keystore/index.js +6 -1
- package/dist/keystore/index.js.map +1 -1
- package/dist/keystore/indexeddb.d.ts +11 -1
- package/dist/keystore/indexeddb.d.ts.map +1 -1
- package/dist/keystore/indexeddb.js +167 -18
- package/dist/keystore/indexeddb.js.map +1 -1
- package/dist/register-flow.d.ts +34 -0
- package/dist/register-flow.d.ts.map +1 -0
- package/dist/register-flow.js +355 -0
- package/dist/register-flow.js.map +1 -0
- package/dist/v2/session/keystore.d.ts +5 -0
- package/dist/v2/session/keystore.d.ts.map +1 -1
- package/dist/v2/session/keystore.js +29 -0
- package/dist/v2/session/keystore.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/_packed_docs/0.4.0_/345/267/256/345/274/202/346/240/270/345/256/236/345/206/263/347/255/226/350/256/260/345/275/225.md +0 -302
- package/_packed_docs/AUN_SDK_0.4.0_/350/256/276/350/256/241/345/257/271/346/257/224/345/210/206/346/236/220.md +0 -194
- package/_packed_docs/AUN_SDK_/351/207/215/346/236/204/345/256/236/346/226/275/350/256/241/345/210/222.md +0 -596
- package/_packed_docs/AUN_SDK_/351/207/215/346/236/204/350/256/276/350/256/241/346/226/271/346/241/210_v3.md +0 -1698
- package/_packed_docs/python-sdk-v2-only-changelog.md +0 -189
package/dist/bundle.js
CHANGED
|
@@ -314,12 +314,12 @@ async function secretPut(storeName, key, value) {
|
|
|
314
314
|
tx.oncomplete = () => db.close();
|
|
315
315
|
});
|
|
316
316
|
}
|
|
317
|
-
var
|
|
317
|
+
var _noopLog7, SECRET_DB_NAME, SECRET_DB_VERSION, STORE_SECRETS, STORE_MASTER, IndexedDBSecretStore;
|
|
318
318
|
var init_indexeddb_store = __esm({
|
|
319
319
|
"src/secret-store/indexeddb-store.ts"() {
|
|
320
320
|
"use strict";
|
|
321
321
|
init_crypto();
|
|
322
|
-
|
|
322
|
+
_noopLog7 = { error: () => {
|
|
323
323
|
}, warn: () => {
|
|
324
324
|
}, info: () => {
|
|
325
325
|
}, debug: () => {
|
|
@@ -332,7 +332,7 @@ var init_indexeddb_store = __esm({
|
|
|
332
332
|
constructor(encryptionSeed) {
|
|
333
333
|
__publicField(this, "_encryptionSeed");
|
|
334
334
|
__publicField(this, "_masterKeyPromise", null);
|
|
335
|
-
__publicField(this, "_log",
|
|
335
|
+
__publicField(this, "_log", _noopLog7);
|
|
336
336
|
this._encryptionSeed = encryptionSeed;
|
|
337
337
|
}
|
|
338
338
|
setLogger(log) {
|
|
@@ -454,7 +454,7 @@ var init_indexeddb_store = __esm({
|
|
|
454
454
|
});
|
|
455
455
|
|
|
456
456
|
// src/version.ts
|
|
457
|
-
var VERSION = "0.4.
|
|
457
|
+
var VERSION = "0.4.5";
|
|
458
458
|
|
|
459
459
|
// src/types.ts
|
|
460
460
|
var ConnectionState = /* @__PURE__ */ ((ConnectionState2) => {
|
|
@@ -2031,13 +2031,14 @@ function gatewayHttpUrl(gatewayUrl, path) {
|
|
|
2031
2031
|
var _AuthFlow = class _AuthFlow {
|
|
2032
2032
|
constructor(opts) {
|
|
2033
2033
|
__publicField(this, "_log", _noopLog4);
|
|
2034
|
-
__publicField(this, "
|
|
2034
|
+
__publicField(this, "_tokenStore");
|
|
2035
2035
|
__publicField(this, "_crypto");
|
|
2036
2036
|
__publicField(this, "_aid");
|
|
2037
2037
|
__publicField(this, "_deviceId");
|
|
2038
2038
|
__publicField(this, "_slotId");
|
|
2039
2039
|
__publicField(this, "_rootCaPem");
|
|
2040
2040
|
__publicField(this, "_verifySsl");
|
|
2041
|
+
__publicField(this, "_memIdentity", null);
|
|
2041
2042
|
// 缓存
|
|
2042
2043
|
__publicField(this, "_rootCerts", null);
|
|
2043
2044
|
__publicField(this, "_gatewayChainCache", /* @__PURE__ */ new Map());
|
|
@@ -2046,7 +2047,7 @@ var _AuthFlow = class _AuthFlow {
|
|
|
2046
2047
|
__publicField(this, "_chainVerifiedCache", /* @__PURE__ */ new Map());
|
|
2047
2048
|
__publicField(this, "_chainCacheTtl");
|
|
2048
2049
|
__publicField(this, "_gatewayCaVerified", /* @__PURE__ */ new Map());
|
|
2049
|
-
this.
|
|
2050
|
+
this._tokenStore = opts.tokenStore;
|
|
2050
2051
|
this._crypto = opts.crypto;
|
|
2051
2052
|
this._aid = opts.aid ?? null;
|
|
2052
2053
|
this._deviceId = String(opts.deviceId ?? "").trim();
|
|
@@ -2059,13 +2060,18 @@ var _AuthFlow = class _AuthFlow {
|
|
|
2059
2060
|
this._log = log;
|
|
2060
2061
|
}
|
|
2061
2062
|
// ── 公开 API ──────────────────────────────────────
|
|
2063
|
+
/** 注入内存私钥,禁止 AuthFlow 内部再走 tokenStore 解密 */
|
|
2064
|
+
setIdentity(identity) {
|
|
2065
|
+
this._memIdentity = identity;
|
|
2066
|
+
if (identity?.aid) this._aid = String(identity.aid);
|
|
2067
|
+
}
|
|
2062
2068
|
/** 加载本地身份信息 */
|
|
2063
2069
|
async loadIdentity(aid) {
|
|
2064
2070
|
const tStart = Date.now();
|
|
2065
2071
|
this._log.debug(`loadIdentity enter: aid=${aid ?? "<current>"}`);
|
|
2066
2072
|
try {
|
|
2067
2073
|
const identity = await this._loadIdentityOrRaise(aid);
|
|
2068
|
-
const cert = await this.
|
|
2074
|
+
const cert = await this._tokenStore.loadCert(identity.aid);
|
|
2069
2075
|
if (cert) identity.cert = cert;
|
|
2070
2076
|
const instanceState = await this._loadInstanceState(identity.aid);
|
|
2071
2077
|
if (instanceState) {
|
|
@@ -2104,93 +2110,10 @@ var _AuthFlow = class _AuthFlow {
|
|
|
2104
2110
|
this._deviceId = String(opts.deviceId ?? "").trim();
|
|
2105
2111
|
this._slotId = String(opts.slotId ?? "").trim();
|
|
2106
2112
|
}
|
|
2107
|
-
/**
|
|
2108
|
-
* 严格注册新 AID(对齐 TS registerAid / Go RegisterAID)。
|
|
2109
|
-
*
|
|
2110
|
-
* 注册与认证彻底分离:此方法绝不被 SDK 内部自动调用,
|
|
2111
|
-
* 必须由应用层显式调用。
|
|
2112
|
-
*/
|
|
2113
|
-
async registerAid(gatewayUrl, aid) {
|
|
2114
|
-
const tStart = Date.now();
|
|
2115
|
-
this._log.debug(`registerAid enter: aid=${aid} gateway=${gatewayUrl}`);
|
|
2116
|
-
_AuthFlow._validateAidName(aid);
|
|
2117
|
-
try {
|
|
2118
|
-
const existing = await this._keystore.loadIdentity(aid);
|
|
2119
|
-
if (existing && existing.private_key_pem && existing.public_key_der_b64) {
|
|
2120
|
-
this._log.debug(`registerAid: local keypair exists, checking server: aid=${aid}`);
|
|
2121
|
-
const localPubB642 = String(existing.public_key_der_b64);
|
|
2122
|
-
const serverCertPem2 = await this._downloadRegisteredCert(gatewayUrl, aid);
|
|
2123
|
-
if (serverCertPem2) {
|
|
2124
|
-
const serverCert = parseCertDer(serverCertPem2);
|
|
2125
|
-
const serverPubB64 = uint8ToBase64(serverCert.spkiBytes);
|
|
2126
|
-
if (serverPubB64 !== localPubB642) {
|
|
2127
|
-
throw new IdentityConflictError(
|
|
2128
|
-
`AID '${aid}' is registered by another party on server (public key mismatch). Choose a different name.`
|
|
2129
|
-
);
|
|
2130
|
-
}
|
|
2131
|
-
this._log.info(`registerAid: idempotent return for already-registered AID: aid=${aid}`);
|
|
2132
|
-
if (!existing.cert) {
|
|
2133
|
-
existing.cert = serverCertPem2;
|
|
2134
|
-
await this._persistIdentity(existing);
|
|
2135
|
-
}
|
|
2136
|
-
this._aid = aid;
|
|
2137
|
-
return { aid, cert: serverCertPem2 };
|
|
2138
|
-
} else {
|
|
2139
|
-
this._log.debug(`registerAid: server has no record, registering with existing keypair: aid=${aid}`);
|
|
2140
|
-
const created2 = await this._createAid(gatewayUrl, existing);
|
|
2141
|
-
const certPem2 = String(created2.cert ?? "");
|
|
2142
|
-
if (!certPem2) {
|
|
2143
|
-
throw new AuthError(`registerAid: server response missing cert for ${aid}`);
|
|
2144
|
-
}
|
|
2145
|
-
existing.cert = certPem2;
|
|
2146
|
-
const returnedCert2 = parseCertDer(certPem2);
|
|
2147
|
-
const certPubB642 = uint8ToBase64(returnedCert2.spkiBytes);
|
|
2148
|
-
if (certPubB642 !== localPubB642) {
|
|
2149
|
-
throw new AuthError(
|
|
2150
|
-
`registerAid: server returned certificate with mismatched public key for ${aid}`
|
|
2151
|
-
);
|
|
2152
|
-
}
|
|
2153
|
-
await this._persistIdentity(existing);
|
|
2154
|
-
this._aid = aid;
|
|
2155
|
-
this._log.debug(`registerAid exit (recovered): elapsed=${Date.now() - tStart}ms aid=${aid}`);
|
|
2156
|
-
return { aid, cert: certPem2 };
|
|
2157
|
-
}
|
|
2158
|
-
}
|
|
2159
|
-
const serverCertPem = await this._downloadRegisteredCert(gatewayUrl, aid);
|
|
2160
|
-
if (serverCertPem) {
|
|
2161
|
-
throw new IdentityConflictError(
|
|
2162
|
-
`AID '${aid}' is already registered on server. Choose a different name, or if you own the keypair use a recovery flow.`
|
|
2163
|
-
);
|
|
2164
|
-
}
|
|
2165
|
-
const identity = await this._crypto.generateIdentity();
|
|
2166
|
-
identity.aid = aid;
|
|
2167
|
-
const created = await this._createAid(gatewayUrl, identity);
|
|
2168
|
-
const certPem = String(created.cert ?? "");
|
|
2169
|
-
if (!certPem) {
|
|
2170
|
-
throw new AuthError(`registerAid: server response missing cert for ${aid}`);
|
|
2171
|
-
}
|
|
2172
|
-
identity.cert = certPem;
|
|
2173
|
-
const returnedCert = parseCertDer(certPem);
|
|
2174
|
-
const certPubB64 = uint8ToBase64(returnedCert.spkiBytes);
|
|
2175
|
-
const localPubB64 = String(identity.public_key_der_b64);
|
|
2176
|
-
if (certPubB64 !== localPubB64) {
|
|
2177
|
-
throw new AuthError(
|
|
2178
|
-
`registerAid: server returned certificate with mismatched public key for ${aid}`
|
|
2179
|
-
);
|
|
2180
|
-
}
|
|
2181
|
-
await this._persistIdentity(identity);
|
|
2182
|
-
this._aid = aid;
|
|
2183
|
-
this._log.debug(`registerAid exit: elapsed=${Date.now() - tStart}ms aid=${aid}`);
|
|
2184
|
-
return { aid: identity.aid, cert: identity.cert };
|
|
2185
|
-
} catch (err) {
|
|
2186
|
-
this._log.debug(`registerAid exit (error): elapsed=${Date.now() - tStart}ms aid=${aid} err=${err instanceof Error ? err.message : String(err)}`);
|
|
2187
|
-
throw err;
|
|
2188
|
-
}
|
|
2189
|
-
}
|
|
2190
2113
|
/**
|
|
2191
2114
|
* 认证已有 AID — login1/login2 双阶段流程。
|
|
2192
2115
|
*
|
|
2193
|
-
* 优先复用
|
|
2116
|
+
* 优先复用 tokenStore 里的 cached access_token(未过期且有 refresh_token),
|
|
2194
2117
|
* 避免每次 authenticate 都走两阶段重登的网络往返。与 Python SDK 行为对齐。
|
|
2195
2118
|
*/
|
|
2196
2119
|
async authenticate(gatewayUrl, aid) {
|
|
@@ -2584,15 +2507,6 @@ var _AuthFlow = class _AuthFlow {
|
|
|
2584
2507
|
clearTimeout(timeoutId);
|
|
2585
2508
|
}
|
|
2586
2509
|
}
|
|
2587
|
-
// ── 内部方法:AID 创建 ───────────────────────────
|
|
2588
|
-
async _createAid(gatewayUrl, identity) {
|
|
2589
|
-
const response = await this._shortRpc(gatewayUrl, "auth.create_aid", {
|
|
2590
|
-
aid: identity.aid,
|
|
2591
|
-
public_key: identity.public_key_der_b64,
|
|
2592
|
-
curve: identity.curve ?? "P-256"
|
|
2593
|
-
});
|
|
2594
|
-
return { cert: response.cert };
|
|
2595
|
-
}
|
|
2596
2510
|
/**
|
|
2597
2511
|
* 从服务端下载指定 AID 的证书(公开 API)。
|
|
2598
2512
|
*
|
|
@@ -3104,52 +3018,53 @@ var _AuthFlow = class _AuthFlow {
|
|
|
3104
3018
|
/** 加载身份,不存在或半成品时抛出异常 */
|
|
3105
3019
|
async _loadIdentityOrRaise(aid) {
|
|
3106
3020
|
const requestedAid = aid ?? this._aid;
|
|
3107
|
-
if (
|
|
3108
|
-
const
|
|
3109
|
-
if (
|
|
3110
|
-
throw new StateError(`identity
|
|
3021
|
+
if (this._memIdentity) {
|
|
3022
|
+
const mem = this._memIdentity;
|
|
3023
|
+
if (requestedAid && String(mem.aid ?? "") !== requestedAid) {
|
|
3024
|
+
throw new StateError(`identity mismatch: requested ${requestedAid}, loaded ${mem.aid}`);
|
|
3111
3025
|
}
|
|
3112
|
-
if (!
|
|
3113
|
-
throw new StateError(
|
|
3114
|
-
`local identity for aid ${requestedAid} is incomplete (missing keypair); call auth.registerAid() first`
|
|
3115
|
-
);
|
|
3026
|
+
if (!mem.private_key_pem || !mem.public_key_der_b64) {
|
|
3027
|
+
throw new StateError(`injected identity for aid ${mem.aid} is incomplete (missing keypair)`);
|
|
3116
3028
|
}
|
|
3117
|
-
this._aid = requestedAid;
|
|
3118
|
-
|
|
3119
|
-
|
|
3029
|
+
if (requestedAid) this._aid = requestedAid;
|
|
3030
|
+
return { ...mem };
|
|
3031
|
+
}
|
|
3032
|
+
if (requestedAid) {
|
|
3033
|
+
throw new StateError(
|
|
3034
|
+
`no injected identity for aid ${requestedAid}; call AUNClient.loadIdentity(aid) first`
|
|
3035
|
+
);
|
|
3120
3036
|
}
|
|
3121
|
-
throw new StateError("no local identity found, call
|
|
3037
|
+
throw new StateError("no local identity found, call AUNClient.loadIdentity(aid) first");
|
|
3122
3038
|
}
|
|
3123
3039
|
// (_ensureIdentity 已移除:注册和登录彻底分离)
|
|
3124
3040
|
async _loadInstanceState(aid) {
|
|
3125
|
-
if (typeof this.
|
|
3041
|
+
if (typeof this._tokenStore.loadInstanceState !== "function") {
|
|
3126
3042
|
return null;
|
|
3127
3043
|
}
|
|
3128
|
-
return await this.
|
|
3044
|
+
return await this._tokenStore.loadInstanceState(aid, this._deviceId, this._slotId);
|
|
3129
3045
|
}
|
|
3130
3046
|
async _persistIdentity(identity) {
|
|
3131
3047
|
const aid = String(identity.aid ?? "");
|
|
3132
3048
|
if (!aid) {
|
|
3133
3049
|
throw new StateError("identity missing aid");
|
|
3134
3050
|
}
|
|
3135
|
-
const persisted = { ...identity };
|
|
3136
3051
|
const instanceState = {};
|
|
3137
3052
|
const instanceStateRecord = instanceState;
|
|
3138
|
-
const persistedRecord =
|
|
3053
|
+
const persistedRecord = { ...identity };
|
|
3139
3054
|
for (const key of _AuthFlow._INSTANCE_STATE_FIELDS) {
|
|
3140
|
-
if (key in
|
|
3055
|
+
if (key in persistedRecord) {
|
|
3141
3056
|
instanceStateRecord[key] = persistedRecord[key];
|
|
3142
3057
|
delete persistedRecord[key];
|
|
3143
3058
|
}
|
|
3144
3059
|
}
|
|
3145
|
-
|
|
3146
|
-
|
|
3060
|
+
const certPem = String(persistedRecord.cert ?? "");
|
|
3061
|
+
if (certPem) {
|
|
3062
|
+
await this._tokenStore.saveCert(aid, certPem);
|
|
3147
3063
|
}
|
|
3148
|
-
|
|
3149
|
-
if (Object.keys(instanceState).length === 0 || typeof this._keystore.updateInstanceState !== "function") {
|
|
3064
|
+
if (Object.keys(instanceState).length === 0 || typeof this._tokenStore.updateInstanceState !== "function") {
|
|
3150
3065
|
return;
|
|
3151
3066
|
}
|
|
3152
|
-
await this.
|
|
3067
|
+
await this._tokenStore.updateInstanceState(aid, this._deviceId, this._slotId, (current) => {
|
|
3153
3068
|
Object.assign(current, instanceState);
|
|
3154
3069
|
return current;
|
|
3155
3070
|
});
|
|
@@ -3571,7 +3486,7 @@ function hasEncryptionSeed(seed) {
|
|
|
3571
3486
|
return seed !== void 0;
|
|
3572
3487
|
}
|
|
3573
3488
|
var DB_NAME = "aun-keystore";
|
|
3574
|
-
var DB_VERSION =
|
|
3489
|
+
var DB_VERSION = 7;
|
|
3575
3490
|
var STORE_KEY_PAIRS = "key_pairs";
|
|
3576
3491
|
var STORE_CERTS = "certs";
|
|
3577
3492
|
var STORE_METADATA = "metadata";
|
|
@@ -3582,10 +3497,18 @@ var STORE_GROUP_OLD_EPOCHS = "group_old_epochs";
|
|
|
3582
3497
|
var STORE_SESSIONS = "e2ee_sessions";
|
|
3583
3498
|
var STORE_GROUP_STATE = "group_state";
|
|
3584
3499
|
var STORE_AGENT_MD_CACHE = "agent_md_cache";
|
|
3500
|
+
var STORE_PENDING_IDENTITIES = "pending_identities";
|
|
3585
3501
|
var STRUCTURED_RECOVERY_RETENTION_MS = 7 * 24 * 3600 * 1e3;
|
|
3586
3502
|
function metadataStoreKey(aid) {
|
|
3587
3503
|
return safeAid(aid);
|
|
3588
3504
|
}
|
|
3505
|
+
function randomHex(byteLength) {
|
|
3506
|
+
const bytes = crypto.getRandomValues(new Uint8Array(byteLength));
|
|
3507
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
3508
|
+
}
|
|
3509
|
+
function pendingIdentityPrefix(aid) {
|
|
3510
|
+
return `${safeAid(aid)}-`;
|
|
3511
|
+
}
|
|
3589
3512
|
function normalizeCertFingerprint(certFingerprint) {
|
|
3590
3513
|
const normalized = String(certFingerprint ?? "").trim().toLowerCase();
|
|
3591
3514
|
if (!normalized) return "";
|
|
@@ -3751,6 +3674,9 @@ function openDB() {
|
|
|
3751
3674
|
if (!db.objectStoreNames.contains(STORE_AGENT_MD_CACHE)) {
|
|
3752
3675
|
db.createObjectStore(STORE_AGENT_MD_CACHE);
|
|
3753
3676
|
}
|
|
3677
|
+
if (!db.objectStoreNames.contains(STORE_PENDING_IDENTITIES)) {
|
|
3678
|
+
db.createObjectStore(STORE_PENDING_IDENTITIES);
|
|
3679
|
+
}
|
|
3754
3680
|
};
|
|
3755
3681
|
request.onsuccess = () => {
|
|
3756
3682
|
const db = request.result;
|
|
@@ -3885,9 +3811,9 @@ async function idbDelete(storeName, key) {
|
|
|
3885
3811
|
var _IndexedDBKeyStore = class _IndexedDBKeyStore {
|
|
3886
3812
|
constructor(opts) {
|
|
3887
3813
|
__publicField(this, "_log", _noopLog5);
|
|
3888
|
-
/**
|
|
3814
|
+
/** 私钥加密种子;空字符串也是有效 seed,默认不再写入明文私钥。 */
|
|
3889
3815
|
__publicField(this, "_encryptionSeed");
|
|
3890
|
-
this._encryptionSeed = opts?.encryptionSeed;
|
|
3816
|
+
this._encryptionSeed = opts?.encryptionSeed ?? "";
|
|
3891
3817
|
}
|
|
3892
3818
|
setLogger(log) {
|
|
3893
3819
|
this._log = log;
|
|
@@ -3901,7 +3827,13 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
|
|
|
3901
3827
|
for (const row of rows) {
|
|
3902
3828
|
if (!isRecord(row.value)) continue;
|
|
3903
3829
|
const envelope = row.value._encrypted_pk;
|
|
3904
|
-
if (!isRecord(envelope))
|
|
3830
|
+
if (!isRecord(envelope)) {
|
|
3831
|
+
const plain = row.value.private_key_pem;
|
|
3832
|
+
if (typeof plain === "string" && plain) {
|
|
3833
|
+
migrations.push({ key: row.key, value: deepClone(row.value), privateKeyPem: plain });
|
|
3834
|
+
}
|
|
3835
|
+
continue;
|
|
3836
|
+
}
|
|
3905
3837
|
try {
|
|
3906
3838
|
const privateKeyPem = await _decryptPEM(envelope, oldSeed);
|
|
3907
3839
|
migrations.push({ key: row.key, value: deepClone(row.value), privateKeyPem });
|
|
@@ -4000,16 +3932,14 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
|
|
|
4000
3932
|
result.private_key_pem = await _decryptPEM(envelope, this._encryptionSeed);
|
|
4001
3933
|
delete result._encrypted_pk;
|
|
4002
3934
|
} catch {
|
|
4003
|
-
this._log.error(`[keystore] decrypt ${aid} private
|
|
3935
|
+
this._log.error(`[keystore] decrypt ${aid} private key failed, maybe encryptionSeed mismatch`);
|
|
3936
|
+
throw new Error(`private key decrypt failed for aid ${aid}: seed_password mismatch or IndexedDB record corrupted`);
|
|
4004
3937
|
}
|
|
4005
3938
|
} else if (
|
|
4006
3939
|
// 透明迁移:旧版明文数据自动加密回写
|
|
4007
3940
|
!epk && typeof result.private_key_pem === "string" && hasEncryptionSeed(this._encryptionSeed)
|
|
4008
3941
|
) {
|
|
4009
|
-
|
|
4010
|
-
await this.saveKeyPair(aid, result);
|
|
4011
|
-
} catch {
|
|
4012
|
-
}
|
|
3942
|
+
await this.saveKeyPair(aid, result);
|
|
4013
3943
|
}
|
|
4014
3944
|
return result;
|
|
4015
3945
|
}
|
|
@@ -4021,6 +3951,123 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
|
|
|
4021
3951
|
}
|
|
4022
3952
|
await idbPut(STORE_KEY_PAIRS, metadataStoreKey(aid), record);
|
|
4023
3953
|
}
|
|
3954
|
+
// ── RegisterAID pending 身份 ─────────────────────────
|
|
3955
|
+
async pendingIdentityDir(aid) {
|
|
3956
|
+
const handle = `${pendingIdentityPrefix(aid)}${randomHex(4)}-${Math.floor(Date.now() / 1e3)}`;
|
|
3957
|
+
const now = Date.now();
|
|
3958
|
+
await idbPut(STORE_PENDING_IDENTITIES, handle, {
|
|
3959
|
+
aid,
|
|
3960
|
+
created_at: now,
|
|
3961
|
+
updated_at: now
|
|
3962
|
+
});
|
|
3963
|
+
return handle;
|
|
3964
|
+
}
|
|
3965
|
+
async listPendingIdentityDirs(aid) {
|
|
3966
|
+
const prefix = pendingIdentityPrefix(aid);
|
|
3967
|
+
const rows = await idbGetAllByPrefix(STORE_PENDING_IDENTITIES, prefix);
|
|
3968
|
+
return rows.filter((row) => row.key.startsWith(prefix) && isRecord(row.value) && String(row.value.aid ?? "") === aid).sort((a, b) => Number(b.value.updated_at ?? 0) - Number(a.value.updated_at ?? 0)).map((row) => row.key);
|
|
3969
|
+
}
|
|
3970
|
+
async savePendingKeyPair(handle, aid, keyPair) {
|
|
3971
|
+
const current = await this._loadPendingRecord(handle, aid);
|
|
3972
|
+
if (!current) throw new Error(`pending identity not found: ${handle}`);
|
|
3973
|
+
const record = deepClone(keyPair);
|
|
3974
|
+
const privateKeyPem = record.private_key_pem;
|
|
3975
|
+
if (typeof privateKeyPem !== "string" || !privateKeyPem) {
|
|
3976
|
+
throw new Error("savePendingKeyPair requires private_key_pem");
|
|
3977
|
+
}
|
|
3978
|
+
record._encrypted_pk = await _encryptPEM(privateKeyPem, this._encryptionSeed ?? "");
|
|
3979
|
+
delete record.private_key_pem;
|
|
3980
|
+
await idbPut(STORE_PENDING_IDENTITIES, handle, {
|
|
3981
|
+
...current,
|
|
3982
|
+
aid,
|
|
3983
|
+
key_pair: record,
|
|
3984
|
+
updated_at: Date.now()
|
|
3985
|
+
});
|
|
3986
|
+
}
|
|
3987
|
+
async loadPendingKeyPair(handle, aid) {
|
|
3988
|
+
const current = await this._loadPendingRecord(handle, aid, false);
|
|
3989
|
+
if (!current || !isRecord(current.key_pair)) return null;
|
|
3990
|
+
const result = deepClone(current.key_pair);
|
|
3991
|
+
const encrypted = result._encrypted_pk;
|
|
3992
|
+
if (isRecord(encrypted)) {
|
|
3993
|
+
try {
|
|
3994
|
+
const envelope = encrypted;
|
|
3995
|
+
result.private_key_pem = await _decryptPEM(envelope, this._encryptionSeed ?? "");
|
|
3996
|
+
delete result._encrypted_pk;
|
|
3997
|
+
return result;
|
|
3998
|
+
} catch {
|
|
3999
|
+
this._log.error(`[keystore] decrypt pending ${aid} private key failed, maybe encryptionSeed mismatch`);
|
|
4000
|
+
throw new Error(`pending identity private key decrypt failed for aid ${aid}: seed_password mismatch or IndexedDB record corrupted`);
|
|
4001
|
+
}
|
|
4002
|
+
}
|
|
4003
|
+
if (typeof result.private_key_pem === "string" && result.private_key_pem) {
|
|
4004
|
+
await this.savePendingKeyPair(handle, aid, result);
|
|
4005
|
+
return result;
|
|
4006
|
+
}
|
|
4007
|
+
return result;
|
|
4008
|
+
}
|
|
4009
|
+
async savePendingCert(handle, certPem) {
|
|
4010
|
+
const current = await this._loadPendingRecord(handle, void 0);
|
|
4011
|
+
if (!current) throw new Error(`pending identity not found: ${handle}`);
|
|
4012
|
+
await idbPut(STORE_PENDING_IDENTITIES, handle, {
|
|
4013
|
+
...current,
|
|
4014
|
+
cert: certPem,
|
|
4015
|
+
updated_at: Date.now()
|
|
4016
|
+
});
|
|
4017
|
+
}
|
|
4018
|
+
async promotePendingIdentity(handle, aid) {
|
|
4019
|
+
const current = await this._loadPendingRecord(handle, aid);
|
|
4020
|
+
if (!current) throw new Error(`pending identity not found: ${handle}`);
|
|
4021
|
+
if (!isRecord(current.key_pair)) {
|
|
4022
|
+
throw new Error(`promotePendingIdentity: missing pending key pair: ${handle}`);
|
|
4023
|
+
}
|
|
4024
|
+
const targetKey = metadataStoreKey(aid);
|
|
4025
|
+
const [existingKeyPair, existingCert, existingMetadata] = await Promise.all([
|
|
4026
|
+
idbGet(STORE_KEY_PAIRS, targetKey),
|
|
4027
|
+
idbGet(STORE_CERTS, certStoreKey(aid)),
|
|
4028
|
+
idbGet(STORE_METADATA, targetKey)
|
|
4029
|
+
]);
|
|
4030
|
+
if (existingKeyPair || existingCert || existingMetadata) {
|
|
4031
|
+
throw new Error(`promotePendingIdentity: target exists: ${targetKey}`);
|
|
4032
|
+
}
|
|
4033
|
+
const keyPair = await this._protectedPendingKeyPair(current, aid);
|
|
4034
|
+
await idbPut(STORE_KEY_PAIRS, targetKey, keyPair);
|
|
4035
|
+
if (typeof current.cert === "string" && current.cert) {
|
|
4036
|
+
await idbPut(STORE_CERTS, certStoreKey(aid), current.cert);
|
|
4037
|
+
}
|
|
4038
|
+
await idbDelete(STORE_PENDING_IDENTITIES, handle);
|
|
4039
|
+
return targetKey;
|
|
4040
|
+
}
|
|
4041
|
+
async _protectedPendingKeyPair(current, aid) {
|
|
4042
|
+
if (!isRecord(current.key_pair)) {
|
|
4043
|
+
throw new Error(`pending identity missing key pair for ${aid}`);
|
|
4044
|
+
}
|
|
4045
|
+
const keyPair = deepClone(current.key_pair);
|
|
4046
|
+
const privateKeyPem = keyPair.private_key_pem;
|
|
4047
|
+
if (typeof privateKeyPem === "string" && privateKeyPem) {
|
|
4048
|
+
throw new Error(`pending identity private key is plaintext for ${aid}`);
|
|
4049
|
+
}
|
|
4050
|
+
if (!isRecord(keyPair._encrypted_pk)) {
|
|
4051
|
+
throw new Error(`pending identity private key is not encrypted for ${aid}`);
|
|
4052
|
+
}
|
|
4053
|
+
return keyPair;
|
|
4054
|
+
}
|
|
4055
|
+
async cleanupPendingDirs(maxAgeMs = 6e5) {
|
|
4056
|
+
const rows = await idbGetAll(STORE_PENDING_IDENTITIES);
|
|
4057
|
+
const now = Date.now();
|
|
4058
|
+
let removed = 0;
|
|
4059
|
+
for (const row of rows) {
|
|
4060
|
+
if (!isRecord(row.value)) continue;
|
|
4061
|
+
const updatedAt = Number(row.value.updated_at ?? row.value.created_at ?? 0);
|
|
4062
|
+
if (updatedAt && now - updatedAt < maxAgeMs) continue;
|
|
4063
|
+
await idbDelete(STORE_PENDING_IDENTITIES, row.key);
|
|
4064
|
+
removed++;
|
|
4065
|
+
}
|
|
4066
|
+
return removed;
|
|
4067
|
+
}
|
|
4068
|
+
async discardPendingIdentity(handle) {
|
|
4069
|
+
await idbDelete(STORE_PENDING_IDENTITIES, handle);
|
|
4070
|
+
}
|
|
4024
4071
|
// ── 证书 ──────────────────────────────────────────
|
|
4025
4072
|
async loadCert(aid, certFingerprint) {
|
|
4026
4073
|
const tStart = Date.now();
|
|
@@ -4387,6 +4434,18 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
|
|
|
4387
4434
|
});
|
|
4388
4435
|
}
|
|
4389
4436
|
// ── 内部辅助 ─────────────────────────────────────────
|
|
4437
|
+
async _loadPendingRecord(handle, aid, required = true) {
|
|
4438
|
+
const data = await idbGet(STORE_PENDING_IDENTITIES, handle);
|
|
4439
|
+
if (!isRecord(data)) {
|
|
4440
|
+
if (required) throw new Error(`pending identity not found: ${handle}`);
|
|
4441
|
+
return null;
|
|
4442
|
+
}
|
|
4443
|
+
const record = deepClone(data);
|
|
4444
|
+
if (aid !== void 0 && String(record.aid ?? "") !== aid) {
|
|
4445
|
+
throw new Error(`pending identity aid mismatch: ${handle}`);
|
|
4446
|
+
}
|
|
4447
|
+
return record;
|
|
4448
|
+
}
|
|
4390
4449
|
async _loadKeyPairUnlocked(aid) {
|
|
4391
4450
|
const data = await idbGet(STORE_KEY_PAIRS, metadataStoreKey(aid));
|
|
4392
4451
|
if (!isRecord(data)) return null;
|
|
@@ -4397,16 +4456,14 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
|
|
|
4397
4456
|
result.private_key_pem = await _decryptPEM(envelope, this._encryptionSeed);
|
|
4398
4457
|
delete result._encrypted_pk;
|
|
4399
4458
|
} catch {
|
|
4400
|
-
this._log.error(`[keystore] decrypt ${aid} private
|
|
4459
|
+
this._log.error(`[keystore] decrypt ${aid} private key failed, maybe encryptionSeed mismatch`);
|
|
4460
|
+
throw new Error(`private key decrypt failed for aid ${aid}: seed_password mismatch or IndexedDB record corrupted`);
|
|
4401
4461
|
}
|
|
4402
4462
|
} else if (
|
|
4403
4463
|
// 透明迁移:旧版明文数据自动加密回写
|
|
4404
4464
|
!isRecord(result._encrypted_pk) && typeof result.private_key_pem === "string" && hasEncryptionSeed(this._encryptionSeed)
|
|
4405
4465
|
) {
|
|
4406
|
-
|
|
4407
|
-
await this._saveKeyPairUnlocked(aid, result);
|
|
4408
|
-
} catch {
|
|
4409
|
-
}
|
|
4466
|
+
await this._saveKeyPairUnlocked(aid, result);
|
|
4410
4467
|
}
|
|
4411
4468
|
return result;
|
|
4412
4469
|
}
|
|
@@ -5465,6 +5522,35 @@ var V2KeyStore = class _V2KeyStore {
|
|
|
5465
5522
|
req.onerror = () => reject(req.error);
|
|
5466
5523
|
});
|
|
5467
5524
|
}
|
|
5525
|
+
async saveGroupIdentity(deviceId, groupAid, privateKeyPem, pubDer) {
|
|
5526
|
+
const record = {
|
|
5527
|
+
device_id: deviceId,
|
|
5528
|
+
key_type: "group_identity",
|
|
5529
|
+
group_id: groupAid,
|
|
5530
|
+
key_id: "",
|
|
5531
|
+
private_key: new TextEncoder().encode(privateKeyPem),
|
|
5532
|
+
public_key: pubDer,
|
|
5533
|
+
created_at: Date.now()
|
|
5534
|
+
};
|
|
5535
|
+
return new Promise((resolve, reject) => {
|
|
5536
|
+
const req = this.store("readwrite").put(record);
|
|
5537
|
+
req.onsuccess = () => resolve();
|
|
5538
|
+
req.onerror = () => reject(req.error);
|
|
5539
|
+
});
|
|
5540
|
+
}
|
|
5541
|
+
async loadGroupIdentity(deviceId, groupAid) {
|
|
5542
|
+
return new Promise((resolve, reject) => {
|
|
5543
|
+
const req = this.store("readonly").get([deviceId, "group_identity", groupAid, ""]);
|
|
5544
|
+
req.onsuccess = () => {
|
|
5545
|
+
const r = req.result;
|
|
5546
|
+
resolve(r ? {
|
|
5547
|
+
privateKeyPem: new TextDecoder().decode(r.private_key),
|
|
5548
|
+
pubDer: new Uint8Array(r.public_key)
|
|
5549
|
+
} : null);
|
|
5550
|
+
};
|
|
5551
|
+
req.onerror = () => reject(req.error);
|
|
5552
|
+
});
|
|
5553
|
+
}
|
|
5468
5554
|
async markGroupSPKUploaded(deviceId, groupId, spkId) {
|
|
5469
5555
|
const record = {
|
|
5470
5556
|
device_id: deviceId,
|
|
@@ -10368,7 +10454,7 @@ var _AUNClient = class _AUNClient {
|
|
|
10368
10454
|
__publicField(this, "_sessionOptions", { ...DEFAULT_SESSION_OPTIONS });
|
|
10369
10455
|
__publicField(this, "_dispatcher");
|
|
10370
10456
|
__publicField(this, "_discovery");
|
|
10371
|
-
__publicField(this, "
|
|
10457
|
+
__publicField(this, "_tokenStore");
|
|
10372
10458
|
__publicField(this, "_auth");
|
|
10373
10459
|
__publicField(this, "_transport");
|
|
10374
10460
|
// E2EE 编排状态(内存缓存)
|
|
@@ -10450,7 +10536,7 @@ var _AUNClient = class _AUNClient {
|
|
|
10450
10536
|
__publicField(this, "_clientLog");
|
|
10451
10537
|
__publicField(this, "_logAuth");
|
|
10452
10538
|
__publicField(this, "_logTransport");
|
|
10453
|
-
__publicField(this, "
|
|
10539
|
+
__publicField(this, "_tokenStoreLog");
|
|
10454
10540
|
__publicField(this, "_logDiscovery");
|
|
10455
10541
|
__publicField(this, "_logEvents");
|
|
10456
10542
|
/**
|
|
@@ -10479,18 +10565,18 @@ var _AUNClient = class _AUNClient {
|
|
|
10479
10565
|
this._clientLog = this._logger.for("aun_core.client");
|
|
10480
10566
|
this._logAuth = this._logger.for("aun_core.auth");
|
|
10481
10567
|
this._logTransport = this._logger.for("aun_core.transport");
|
|
10482
|
-
this.
|
|
10568
|
+
this._tokenStoreLog = this._logger.for("aun_core.keystore");
|
|
10483
10569
|
this._logDiscovery = this._logger.for("aun_core.discovery");
|
|
10484
10570
|
this._logEvents = this._logger.for("aun_core.events");
|
|
10485
10571
|
this._clientLog.info(`AUNClient initialized: debug=${_debug} aunPath=${this.configModel.aunPath} aid=${initAid ?? "-"}`);
|
|
10486
10572
|
this._dispatcher = new EventDispatcher();
|
|
10487
10573
|
this._discovery = new GatewayDiscovery();
|
|
10488
|
-
this.
|
|
10574
|
+
this._tokenStore = new IndexedDBKeyStore({});
|
|
10489
10575
|
this._slotId = inputAid?.slotId || "default";
|
|
10490
10576
|
this._connectDeliveryMode = normalizeDeliveryModeConfig({ mode: "fanout" });
|
|
10491
10577
|
this._defaultConnectDeliveryMode = { ...this._connectDeliveryMode };
|
|
10492
10578
|
this._auth = new AuthFlow({
|
|
10493
|
-
|
|
10579
|
+
tokenStore: this._tokenStore,
|
|
10494
10580
|
crypto: new CryptoProvider(),
|
|
10495
10581
|
aid: initAid,
|
|
10496
10582
|
deviceId: this._deviceId,
|
|
@@ -10518,6 +10604,7 @@ var _AUNClient = class _AUNClient {
|
|
|
10518
10604
|
public_key_der_b64: inputAid.publicKey,
|
|
10519
10605
|
cert: inputAid.certPem
|
|
10520
10606
|
};
|
|
10607
|
+
this._auth.setIdentity(this._identity);
|
|
10521
10608
|
this._state = "disconnected";
|
|
10522
10609
|
}
|
|
10523
10610
|
}
|
|
@@ -10527,8 +10614,8 @@ var _AUNClient = class _AUNClient {
|
|
|
10527
10614
|
if (typeof this._discovery.setLogger === "function") {
|
|
10528
10615
|
this._discovery.setLogger(this._logger.for("aun_core.discovery"));
|
|
10529
10616
|
}
|
|
10530
|
-
if (typeof this.
|
|
10531
|
-
this.
|
|
10617
|
+
if (typeof this._tokenStore.setLogger === "function") {
|
|
10618
|
+
this._tokenStore.setLogger(this._tokenStoreLog);
|
|
10532
10619
|
}
|
|
10533
10620
|
this._dispatcher.subscribe("_raw.message.received", (data) => {
|
|
10534
10621
|
this._onRawMessageReceived(data);
|
|
@@ -10719,7 +10806,7 @@ var _AUNClient = class _AUNClient {
|
|
|
10719
10806
|
if (!target) throw new ValidationError("verifyAgentMd requires non-empty aid");
|
|
10720
10807
|
let peer = target === this._currentAid?.aid ? this._currentAid : null;
|
|
10721
10808
|
if (!peer) {
|
|
10722
|
-
let certPem = String(await this.
|
|
10809
|
+
let certPem = String(await this._tokenStore.loadCert(target) ?? "").trim();
|
|
10723
10810
|
if (!certPem) {
|
|
10724
10811
|
certPem = String(await this._fetchPeerCert(target) ?? "").trim();
|
|
10725
10812
|
}
|
|
@@ -10866,11 +10953,11 @@ var _AUNClient = class _AUNClient {
|
|
|
10866
10953
|
async _readAgentMdStorage(logicalKey) {
|
|
10867
10954
|
const key = String(logicalKey ?? "").trim();
|
|
10868
10955
|
if (!key) return null;
|
|
10869
|
-
const load = this.
|
|
10956
|
+
const load = this._tokenStore.loadAgentMdCache;
|
|
10870
10957
|
if (typeof load !== "function") {
|
|
10871
10958
|
throw new Error("IndexedDB agent.md storage unavailable");
|
|
10872
10959
|
}
|
|
10873
|
-
const record = await load.call(this.
|
|
10960
|
+
const record = await load.call(this._tokenStore, this._agentMdRoot(), key);
|
|
10874
10961
|
if (record && Object.prototype.hasOwnProperty.call(record, "content")) {
|
|
10875
10962
|
return String(record.content ?? "");
|
|
10876
10963
|
}
|
|
@@ -10879,12 +10966,12 @@ var _AUNClient = class _AUNClient {
|
|
|
10879
10966
|
async _writeAgentMdStorage(logicalKey, content) {
|
|
10880
10967
|
const key = String(logicalKey ?? "").trim();
|
|
10881
10968
|
if (!key) return;
|
|
10882
|
-
const save = this.
|
|
10969
|
+
const save = this._tokenStore.upsertAgentMdCache;
|
|
10883
10970
|
if (typeof save !== "function") {
|
|
10884
10971
|
throw new Error("IndexedDB agent.md storage unavailable");
|
|
10885
10972
|
}
|
|
10886
10973
|
const text = String(content ?? "");
|
|
10887
|
-
await save.call(this.
|
|
10974
|
+
await save.call(this._tokenStore, this._agentMdRoot(), key, {
|
|
10888
10975
|
content: text,
|
|
10889
10976
|
local_etag: await this._agentMdContentEtag(text),
|
|
10890
10977
|
fetched_at: Date.now()
|
|
@@ -11263,13 +11350,13 @@ var _AUNClient = class _AUNClient {
|
|
|
11263
11350
|
this._clientLog = this._logger.for("aun_core.client");
|
|
11264
11351
|
this._logAuth = this._logger.for("aun_core.auth");
|
|
11265
11352
|
this._logTransport = this._logger.for("aun_core.transport");
|
|
11266
|
-
this.
|
|
11353
|
+
this._tokenStoreLog = this._logger.for("aun_core.keystore");
|
|
11267
11354
|
this._logDiscovery = this._logger.for("aun_core.discovery");
|
|
11268
11355
|
this._logEvents = this._logger.for("aun_core.events");
|
|
11269
11356
|
this._discovery = new GatewayDiscovery();
|
|
11270
|
-
this.
|
|
11357
|
+
this._tokenStore = new IndexedDBKeyStore({});
|
|
11271
11358
|
this._auth = new AuthFlow({
|
|
11272
|
-
|
|
11359
|
+
tokenStore: this._tokenStore,
|
|
11273
11360
|
crypto: new CryptoProvider(),
|
|
11274
11361
|
aid: aid.aid,
|
|
11275
11362
|
deviceId: this._deviceId,
|
|
@@ -11293,8 +11380,8 @@ var _AUNClient = class _AUNClient {
|
|
|
11293
11380
|
if (typeof this._discovery.setLogger === "function") {
|
|
11294
11381
|
this._discovery.setLogger(this._logDiscovery);
|
|
11295
11382
|
}
|
|
11296
|
-
if (typeof this.
|
|
11297
|
-
this.
|
|
11383
|
+
if (typeof this._tokenStore.setLogger === "function") {
|
|
11384
|
+
this._tokenStore.setLogger(this._tokenStoreLog);
|
|
11298
11385
|
}
|
|
11299
11386
|
}
|
|
11300
11387
|
loadIdentity(aid) {
|
|
@@ -11312,6 +11399,7 @@ var _AUNClient = class _AUNClient {
|
|
|
11312
11399
|
public_key_der_b64: aid.publicKey,
|
|
11313
11400
|
cert: aid.certPem
|
|
11314
11401
|
};
|
|
11402
|
+
this._auth.setIdentity(this._identity);
|
|
11315
11403
|
this._state = "disconnected";
|
|
11316
11404
|
this._closing = false;
|
|
11317
11405
|
}
|
|
@@ -12571,8 +12659,8 @@ var _AUNClient = class _AUNClient {
|
|
|
12571
12659
|
const keyEpoch = Number(d.key_epoch ?? 0);
|
|
12572
12660
|
const membershipSnapshot = String(d.membership_snapshot ?? "").trim();
|
|
12573
12661
|
const policySnapshot = String(d.policy_snapshot ?? "").trim();
|
|
12574
|
-
const loadFn = this.
|
|
12575
|
-
const localState = loadFn ? await loadFn.call(this.
|
|
12662
|
+
const loadFn = this._tokenStore.loadGroupState;
|
|
12663
|
+
const localState = loadFn ? await loadFn.call(this._tokenStore, groupId) : null;
|
|
12576
12664
|
if (localState && localState.state_hash && localState.state_hash !== prevStateHash) {
|
|
12577
12665
|
this._clientLog.warn(
|
|
12578
12666
|
"[aun_core] state_hash \u94FE\u4E0D\u8FDE\u7EED group=%s local_sv=%d event_sv=%d",
|
|
@@ -12611,9 +12699,9 @@ var _AUNClient = class _AUNClient {
|
|
|
12611
12699
|
return;
|
|
12612
12700
|
}
|
|
12613
12701
|
}
|
|
12614
|
-
const saveFn2 = this.
|
|
12702
|
+
const saveFn2 = this._tokenStore.saveGroupState;
|
|
12615
12703
|
if (saveFn2) {
|
|
12616
|
-
await saveFn2.call(this.
|
|
12704
|
+
await saveFn2.call(this._tokenStore, groupId, {
|
|
12617
12705
|
group_id: groupId,
|
|
12618
12706
|
state_version: sv,
|
|
12619
12707
|
state_hash: sHash,
|
|
@@ -12649,9 +12737,9 @@ var _AUNClient = class _AUNClient {
|
|
|
12649
12737
|
);
|
|
12650
12738
|
return;
|
|
12651
12739
|
}
|
|
12652
|
-
const saveFn = this.
|
|
12740
|
+
const saveFn = this._tokenStore.saveGroupState;
|
|
12653
12741
|
if (saveFn) {
|
|
12654
|
-
await saveFn.call(this.
|
|
12742
|
+
await saveFn.call(this._tokenStore, groupId, {
|
|
12655
12743
|
group_id: groupId,
|
|
12656
12744
|
state_version: stateVersion,
|
|
12657
12745
|
state_hash: stateHash,
|
|
@@ -13022,7 +13110,7 @@ var _AUNClient = class _AUNClient {
|
|
|
13022
13110
|
refreshAfter: now + PEER_CERT_CACHE_TTL
|
|
13023
13111
|
});
|
|
13024
13112
|
try {
|
|
13025
|
-
await this.
|
|
13113
|
+
await this._tokenStore.saveCert(aid, certPem, certFingerprint, { makeActive: false });
|
|
13026
13114
|
} catch (exc) {
|
|
13027
13115
|
this._clientLog.error(`write cert to keystore failed (aid=${aid}): ${String(exc)}`, exc instanceof Error ? exc : void 0);
|
|
13028
13116
|
}
|
|
@@ -13039,7 +13127,7 @@ var _AUNClient = class _AUNClient {
|
|
|
13039
13127
|
const cached = this._certCache.get(cacheKey);
|
|
13040
13128
|
const now = Date.now() / 1e3;
|
|
13041
13129
|
if (cached && now < cached.refreshAfter) return true;
|
|
13042
|
-
const localCert = await this.
|
|
13130
|
+
const localCert = await this._tokenStore.loadCert(aid, certFingerprint);
|
|
13043
13131
|
if (localCert) {
|
|
13044
13132
|
if (certFingerprint) {
|
|
13045
13133
|
const actualFingerprint = await this._certFingerprint(localCert);
|
|
@@ -13062,7 +13150,7 @@ var _AUNClient = class _AUNClient {
|
|
|
13062
13150
|
}
|
|
13063
13151
|
try {
|
|
13064
13152
|
const certPem = await this._fetchPeerCert(aid, certFingerprint);
|
|
13065
|
-
await this.
|
|
13153
|
+
await this._tokenStore.saveCert(aid, certPem, certFingerprint, { makeActive: false });
|
|
13066
13154
|
return true;
|
|
13067
13155
|
} catch (exc) {
|
|
13068
13156
|
if (cached && now < cached.validatedAt + PEER_CERT_CACHE_TTL * 2) {
|
|
@@ -13258,8 +13346,8 @@ var _AUNClient = class _AUNClient {
|
|
|
13258
13346
|
if (!target) throw new StateError("gateway discovery requires a loaded AID");
|
|
13259
13347
|
if (this._gatewayUrl) return this._gatewayUrl;
|
|
13260
13348
|
try {
|
|
13261
|
-
const getMetadata = this.
|
|
13262
|
-
const raw = typeof getMetadata === "function" ? String(await getMetadata.call(this.
|
|
13349
|
+
const getMetadata = this._tokenStore.getMetadata;
|
|
13350
|
+
const raw = typeof getMetadata === "function" ? String(await getMetadata.call(this._tokenStore, target, "gateway_url") ?? "").trim() : "";
|
|
13263
13351
|
if (raw) {
|
|
13264
13352
|
const gateway = raw.startsWith('"') && raw.endsWith('"') ? String(JSON.parse(raw)).trim() : raw;
|
|
13265
13353
|
if (gateway) {
|
|
@@ -13282,9 +13370,9 @@ var _AUNClient = class _AUNClient {
|
|
|
13282
13370
|
const gateway = await this._discovery.discover(url);
|
|
13283
13371
|
this._gatewayUrl = gateway;
|
|
13284
13372
|
try {
|
|
13285
|
-
const setMetadata = this.
|
|
13373
|
+
const setMetadata = this._tokenStore.setMetadata;
|
|
13286
13374
|
if (typeof setMetadata === "function") {
|
|
13287
|
-
await setMetadata.call(this.
|
|
13375
|
+
await setMetadata.call(this._tokenStore, target, "gateway_url", gateway);
|
|
13288
13376
|
}
|
|
13289
13377
|
} catch {
|
|
13290
13378
|
}
|
|
@@ -13316,13 +13404,8 @@ var _AUNClient = class _AUNClient {
|
|
|
13316
13404
|
return [gateway];
|
|
13317
13405
|
}
|
|
13318
13406
|
async _syncIdentityAfterConnect(accessToken) {
|
|
13319
|
-
|
|
13320
|
-
try {
|
|
13321
|
-
identity = await this._auth.loadIdentityOrNone(this._aid ?? void 0);
|
|
13322
|
-
} catch {
|
|
13323
|
-
}
|
|
13407
|
+
const identity = this._identity;
|
|
13324
13408
|
if (!identity) {
|
|
13325
|
-
this._identity = null;
|
|
13326
13409
|
return;
|
|
13327
13410
|
}
|
|
13328
13411
|
identity.access_token = accessToken;
|
|
@@ -13332,8 +13415,6 @@ var _AUNClient = class _AUNClient {
|
|
|
13332
13415
|
const persistIdentity = this._auth._persistIdentity;
|
|
13333
13416
|
if (typeof persistIdentity === "function") {
|
|
13334
13417
|
await persistIdentity.call(this._auth, identity);
|
|
13335
|
-
} else {
|
|
13336
|
-
await this._keystore.saveIdentity(String(identity.aid), identity);
|
|
13337
13418
|
}
|
|
13338
13419
|
}
|
|
13339
13420
|
}
|
|
@@ -13803,8 +13884,7 @@ var _AUNClient = class _AUNClient {
|
|
|
13803
13884
|
}
|
|
13804
13885
|
// ── Named Group(命名群)高层 API ────────────────────────────
|
|
13805
13886
|
/**
|
|
13806
|
-
*
|
|
13807
|
-
* 服务端签发群 AID 证书,返回后将证书和私钥存入 keystore。
|
|
13887
|
+
* 创建命名群:群/P2P 私钥由 V2 数据库存储,不写入 AID 身份私钥存储。
|
|
13808
13888
|
*/
|
|
13809
13889
|
async createNamedGroup(groupName, opts = {}) {
|
|
13810
13890
|
const tStart = Date.now();
|
|
@@ -13824,15 +13904,10 @@ var _AUNClient = class _AUNClient {
|
|
|
13824
13904
|
const aidCert = result?.aid_cert;
|
|
13825
13905
|
const groupAid = String(groupInfo?.group_aid ?? "");
|
|
13826
13906
|
if (groupAid && aidCert) {
|
|
13827
|
-
await this.
|
|
13828
|
-
private_key_pem: identity.private_key_pem,
|
|
13829
|
-
public_key: identity.public_key_der_b64,
|
|
13830
|
-
curve: "P-256",
|
|
13831
|
-
type: "group_identity"
|
|
13832
|
-
});
|
|
13907
|
+
await this._saveGroupIdentityToV2(groupAid, identity);
|
|
13833
13908
|
const certPem = String(aidCert.cert ?? "");
|
|
13834
13909
|
if (certPem) {
|
|
13835
|
-
await this.
|
|
13910
|
+
await this._tokenStore.saveCert(groupAid, certPem);
|
|
13836
13911
|
}
|
|
13837
13912
|
}
|
|
13838
13913
|
this._clientLog.debug(`createNamedGroup exit: elapsed=${Date.now() - tStart}ms group_aid=${groupAid}`);
|
|
@@ -13862,15 +13937,10 @@ var _AUNClient = class _AUNClient {
|
|
|
13862
13937
|
const aidCert = result?.aid_cert;
|
|
13863
13938
|
const groupAid = String(groupInfo?.group_aid ?? "");
|
|
13864
13939
|
if (groupAid && aidCert) {
|
|
13865
|
-
await this.
|
|
13866
|
-
private_key_pem: identity.private_key_pem,
|
|
13867
|
-
public_key: identity.public_key_der_b64,
|
|
13868
|
-
curve: "P-256",
|
|
13869
|
-
type: "group_identity"
|
|
13870
|
-
});
|
|
13940
|
+
await this._saveGroupIdentityToV2(groupAid, identity);
|
|
13871
13941
|
const certPem = String(aidCert.cert ?? "");
|
|
13872
13942
|
if (certPem) {
|
|
13873
|
-
await this.
|
|
13943
|
+
await this._tokenStore.saveCert(groupAid, certPem);
|
|
13874
13944
|
}
|
|
13875
13945
|
}
|
|
13876
13946
|
this._clientLog.debug(`bindGroupAid exit: elapsed=${Date.now() - tStart}ms group_aid=${groupAid}`);
|
|
@@ -13906,7 +13976,7 @@ var _AUNClient = class _AUNClient {
|
|
|
13906
13976
|
const deviceId = this._deviceId;
|
|
13907
13977
|
const slotId = this._slotId;
|
|
13908
13978
|
try {
|
|
13909
|
-
const loadAll = this.
|
|
13979
|
+
const loadAll = this._tokenStore.loadAllSeqs?.bind(this._tokenStore);
|
|
13910
13980
|
if (typeof loadAll === "function") {
|
|
13911
13981
|
let state = await loadAll(aid, deviceId, slotId);
|
|
13912
13982
|
if (this._seqTrackerContext !== context) return;
|
|
@@ -13916,7 +13986,7 @@ var _AUNClient = class _AUNClient {
|
|
|
13916
13986
|
}
|
|
13917
13987
|
return;
|
|
13918
13988
|
}
|
|
13919
|
-
const loader = this.
|
|
13989
|
+
const loader = this._tokenStore.loadInstanceState?.bind(this._tokenStore);
|
|
13920
13990
|
if (typeof loader !== "function") return;
|
|
13921
13991
|
const stateHolder = await loader(aid, deviceId, slotId);
|
|
13922
13992
|
if (this._seqTrackerContext !== context) return;
|
|
@@ -13969,8 +14039,8 @@ var _AUNClient = class _AUNClient {
|
|
|
13969
14039
|
const aid = this._aid;
|
|
13970
14040
|
const deviceId = this._deviceId;
|
|
13971
14041
|
const slotId = this._slotId;
|
|
13972
|
-
const saver = this.
|
|
13973
|
-
const deleter = this.
|
|
14042
|
+
const saver = this._tokenStore.saveSeq?.bind(this._tokenStore);
|
|
14043
|
+
const deleter = this._tokenStore.deleteSeq?.bind(this._tokenStore);
|
|
13974
14044
|
if (typeof saver === "function") {
|
|
13975
14045
|
for (const [oldNs, newNs] of Object.entries(renameMap)) {
|
|
13976
14046
|
if (typeof deleter === "function") {
|
|
@@ -14037,7 +14107,7 @@ var _AUNClient = class _AUNClient {
|
|
|
14037
14107
|
const state = this._seqTracker.exportState();
|
|
14038
14108
|
if (Object.keys(state).length === 0) return;
|
|
14039
14109
|
try {
|
|
14040
|
-
const saveFn = this.
|
|
14110
|
+
const saveFn = this._tokenStore.saveSeq?.bind(this._tokenStore);
|
|
14041
14111
|
if (typeof saveFn === "function") {
|
|
14042
14112
|
for (const [ns, seq] of Object.entries(state)) {
|
|
14043
14113
|
saveFn(this._aid, this._deviceId, this._slotId, ns, seq).catch((exc) => {
|
|
@@ -14053,8 +14123,8 @@ var _AUNClient = class _AUNClient {
|
|
|
14053
14123
|
}
|
|
14054
14124
|
return;
|
|
14055
14125
|
}
|
|
14056
|
-
if (typeof this.
|
|
14057
|
-
this.
|
|
14126
|
+
if (typeof this._tokenStore.updateInstanceState === "function") {
|
|
14127
|
+
this._tokenStore.updateInstanceState(this._aid, this._deviceId, this._slotId, (current) => {
|
|
14058
14128
|
current.seq_tracker_state = state;
|
|
14059
14129
|
return current;
|
|
14060
14130
|
}).catch((exc) => {
|
|
@@ -14083,15 +14153,15 @@ var _AUNClient = class _AUNClient {
|
|
|
14083
14153
|
if (!this._aid || !ns) return;
|
|
14084
14154
|
const seq = this._seqTracker.getContiguousSeq(ns);
|
|
14085
14155
|
try {
|
|
14086
|
-
if (seq > 0 && typeof this.
|
|
14087
|
-
this.
|
|
14156
|
+
if (seq > 0 && typeof this._tokenStore.saveSeq === "function") {
|
|
14157
|
+
this._tokenStore.saveSeq(this._aid, this._deviceId, this._slotId, ns, seq).catch((exc) => {
|
|
14088
14158
|
this._clientLog.debug(`persist repaired seq failed: ns=${ns} err=${formatCaughtError(exc)}`);
|
|
14089
14159
|
});
|
|
14090
14160
|
return;
|
|
14091
14161
|
}
|
|
14092
|
-
const deleteSeq = this.
|
|
14162
|
+
const deleteSeq = this._tokenStore.deleteSeq;
|
|
14093
14163
|
if (seq <= 0 && typeof deleteSeq === "function") {
|
|
14094
|
-
deleteSeq.call(this.
|
|
14164
|
+
deleteSeq.call(this._tokenStore, this._aid, this._deviceId, this._slotId, ns).catch((exc) => {
|
|
14095
14165
|
this._clientLog.debug(`delete repaired seq failed: ns=${ns} err=${formatCaughtError(exc)}`);
|
|
14096
14166
|
});
|
|
14097
14167
|
return;
|
|
@@ -14190,6 +14260,25 @@ var _AUNClient = class _AUNClient {
|
|
|
14190
14260
|
this._clientLog.debug(`V2 session initialized aid=${this._aid} device=${this._deviceId}`);
|
|
14191
14261
|
this._safeAsync(this._v2AutoConfirmPendingProposals());
|
|
14192
14262
|
}
|
|
14263
|
+
_v2StoreDeviceId() {
|
|
14264
|
+
return `aid:${encodeURIComponent(String(this._aid ?? ""))}|device:${encodeURIComponent(String(this._deviceId ?? ""))}`;
|
|
14265
|
+
}
|
|
14266
|
+
async _saveGroupIdentityToV2(groupAid, identity) {
|
|
14267
|
+
const privateKeyPem = String(identity.private_key_pem ?? "").trim();
|
|
14268
|
+
const publicKeyDerB642 = String(identity.public_key_der_b64 ?? "").trim();
|
|
14269
|
+
if (!groupAid || !privateKeyPem || !publicKeyDerB642) {
|
|
14270
|
+
throw new StateError("group identity is incomplete");
|
|
14271
|
+
}
|
|
14272
|
+
if (!this._v2KeyStore) {
|
|
14273
|
+
this._v2KeyStore = await V2KeyStore.open();
|
|
14274
|
+
}
|
|
14275
|
+
await this._v2KeyStore.saveGroupIdentity(
|
|
14276
|
+
this._v2StoreDeviceId(),
|
|
14277
|
+
groupAid,
|
|
14278
|
+
privateKeyPem,
|
|
14279
|
+
base64ToUint8(publicKeyDerB642)
|
|
14280
|
+
);
|
|
14281
|
+
}
|
|
14193
14282
|
async _v2TrustedIKPubDer(aid) {
|
|
14194
14283
|
const normalizedAid = String(aid ?? "").trim();
|
|
14195
14284
|
if (!normalizedAid) throw new E2EEError("spk_aid_missing");
|
|
@@ -16086,6 +16175,334 @@ function _uuidV4() {
|
|
|
16086
16175
|
|
|
16087
16176
|
// src/aid-store.ts
|
|
16088
16177
|
init_crypto();
|
|
16178
|
+
|
|
16179
|
+
// src/register-flow.ts
|
|
16180
|
+
init_crypto();
|
|
16181
|
+
var _noopLog6 = { error: () => {
|
|
16182
|
+
}, warn: () => {
|
|
16183
|
+
}, info: () => {
|
|
16184
|
+
}, debug: () => {
|
|
16185
|
+
} };
|
|
16186
|
+
function _gatewayHttpUrl(gatewayUrl, path) {
|
|
16187
|
+
try {
|
|
16188
|
+
const parsed = new URL(gatewayUrl);
|
|
16189
|
+
const scheme = parsed.protocol === "wss:" ? "https:" : "http:";
|
|
16190
|
+
return `${scheme}//${parsed.host}${path}`;
|
|
16191
|
+
} catch {
|
|
16192
|
+
return gatewayUrl.replace(/^wss:/, "https:").replace(/^ws:/, "http:") + path;
|
|
16193
|
+
}
|
|
16194
|
+
}
|
|
16195
|
+
function _extractSpkiB64FromPem(certPem) {
|
|
16196
|
+
const der = new Uint8Array(pemToArrayBuffer(certPem));
|
|
16197
|
+
function readLen(data, offset) {
|
|
16198
|
+
const first = data[offset];
|
|
16199
|
+
if (!(first & 128)) return { value: first, lenBytes: 1 };
|
|
16200
|
+
const n = first & 127;
|
|
16201
|
+
let v = 0;
|
|
16202
|
+
for (let i = 0; i < n; i++) v = v << 8 | data[offset + 1 + i];
|
|
16203
|
+
return { value: v, lenBytes: 1 + n };
|
|
16204
|
+
}
|
|
16205
|
+
function skipTlv(data, offset) {
|
|
16206
|
+
const len = readLen(data, offset + 1);
|
|
16207
|
+
return offset + 1 + len.lenBytes + len.value;
|
|
16208
|
+
}
|
|
16209
|
+
const certLen = readLen(der, 1);
|
|
16210
|
+
const tbsStart = 1 + certLen.lenBytes;
|
|
16211
|
+
const tbsLen = readLen(der, tbsStart + 1);
|
|
16212
|
+
let pos = tbsStart + 1 + tbsLen.lenBytes;
|
|
16213
|
+
if (der[pos] === 160) pos = skipTlv(der, pos);
|
|
16214
|
+
pos = skipTlv(der, pos);
|
|
16215
|
+
pos = skipTlv(der, pos);
|
|
16216
|
+
pos = skipTlv(der, pos);
|
|
16217
|
+
pos = skipTlv(der, pos);
|
|
16218
|
+
pos = skipTlv(der, pos);
|
|
16219
|
+
const spkiLen = readLen(der, pos + 1);
|
|
16220
|
+
const spkiEnd = pos + 1 + spkiLen.lenBytes + spkiLen.value;
|
|
16221
|
+
return uint8ToBase64(der.slice(pos, spkiEnd));
|
|
16222
|
+
}
|
|
16223
|
+
var RegisterFlow = class {
|
|
16224
|
+
constructor(opts) {
|
|
16225
|
+
__publicField(this, "_keystore");
|
|
16226
|
+
__publicField(this, "_crypto");
|
|
16227
|
+
__publicField(this, "_logger");
|
|
16228
|
+
this._keystore = opts.keystore;
|
|
16229
|
+
this._crypto = opts.crypto;
|
|
16230
|
+
this._logger = opts.logger ?? _noopLog6;
|
|
16231
|
+
}
|
|
16232
|
+
async registerAid(gatewayUrl, aid) {
|
|
16233
|
+
const tStart = Date.now();
|
|
16234
|
+
AuthFlow._validateAidName(aid);
|
|
16235
|
+
this._logger.debug(`registerAid enter: aid=${aid}, gateway=${gatewayUrl}`);
|
|
16236
|
+
try {
|
|
16237
|
+
const existing = await this._keystore.loadIdentity(aid);
|
|
16238
|
+
if (existing && existing.private_key_pem && existing.public_key_der_b64) {
|
|
16239
|
+
this._logger.debug(`registerAid: local keypair exists, checking server: aid=${aid}`);
|
|
16240
|
+
const localPubB64 = String(existing.public_key_der_b64);
|
|
16241
|
+
const serverCertPem = await this._downloadRegisteredCert(gatewayUrl, aid);
|
|
16242
|
+
if (serverCertPem) {
|
|
16243
|
+
const serverPubB64 = _extractSpkiB64FromPem(serverCertPem);
|
|
16244
|
+
if (serverPubB64 !== localPubB64) {
|
|
16245
|
+
throw new IdentityConflictError(
|
|
16246
|
+
`AID '${aid}' is registered by another party on server (public key mismatch). Choose a different name.`
|
|
16247
|
+
);
|
|
16248
|
+
}
|
|
16249
|
+
this._logger.info(`registerAid: idempotent return for already-registered AID: aid=${aid}`);
|
|
16250
|
+
if (!existing.cert) {
|
|
16251
|
+
existing.cert = serverCertPem;
|
|
16252
|
+
await this._persistIdentity(existing);
|
|
16253
|
+
}
|
|
16254
|
+
this._logger.debug(`registerAid exit (idempotent): elapsed=${Date.now() - tStart}ms aid=${aid}`);
|
|
16255
|
+
return {
|
|
16256
|
+
aid,
|
|
16257
|
+
cert: serverCertPem,
|
|
16258
|
+
private_key_pem: String(existing.private_key_pem),
|
|
16259
|
+
public_key_der_b64: localPubB64,
|
|
16260
|
+
curve: String(existing.curve ?? "P-256")
|
|
16261
|
+
};
|
|
16262
|
+
} else {
|
|
16263
|
+
this._logger.debug(`registerAid: server has no record, registering with existing keypair: aid=${aid}`);
|
|
16264
|
+
const created2 = await this._createAid(gatewayUrl, existing);
|
|
16265
|
+
const certPem2 = String(created2.cert ?? "");
|
|
16266
|
+
if (!certPem2) throw new AuthError(`registerAid: server response missing cert for ${aid}`);
|
|
16267
|
+
existing.cert = certPem2;
|
|
16268
|
+
this._assertCertMatchesLocalKeypair(existing);
|
|
16269
|
+
await this._persistIdentity(existing);
|
|
16270
|
+
this._logger.debug(`registerAid exit (recovered): elapsed=${Date.now() - tStart}ms aid=${aid}`);
|
|
16271
|
+
return {
|
|
16272
|
+
aid,
|
|
16273
|
+
cert: certPem2,
|
|
16274
|
+
private_key_pem: String(existing.private_key_pem),
|
|
16275
|
+
public_key_der_b64: localPubB64,
|
|
16276
|
+
curve: String(existing.curve ?? "P-256")
|
|
16277
|
+
};
|
|
16278
|
+
}
|
|
16279
|
+
}
|
|
16280
|
+
const recovered = await this._tryRecoverPendingRegistration(gatewayUrl, aid);
|
|
16281
|
+
if (recovered !== null) {
|
|
16282
|
+
this._logger.info(`registerAid recovered from pending: aid=${aid}`);
|
|
16283
|
+
return recovered;
|
|
16284
|
+
}
|
|
16285
|
+
const existingCert = await this._downloadRegisteredCert(gatewayUrl, aid);
|
|
16286
|
+
if (existingCert) {
|
|
16287
|
+
throw new IdentityConflictError(
|
|
16288
|
+
`AID '${aid}' is already registered on server. Choose a different name, or if you own the keypair use a recovery flow.`
|
|
16289
|
+
);
|
|
16290
|
+
}
|
|
16291
|
+
const identity = await this._crypto.generateIdentity();
|
|
16292
|
+
identity.aid = aid;
|
|
16293
|
+
const pendingStore = this._pendingStore();
|
|
16294
|
+
const pendingHandle = await pendingStore.pendingIdentityDir(aid);
|
|
16295
|
+
await pendingStore.savePendingKeyPair(pendingHandle, aid, identity);
|
|
16296
|
+
let created;
|
|
16297
|
+
try {
|
|
16298
|
+
created = await this._createAid(gatewayUrl, identity);
|
|
16299
|
+
} catch (e) {
|
|
16300
|
+
this._logger.warn(`registerAid RPC failed (pending kept for recovery): aid=${aid}, pending=${pendingHandle}, error=${e instanceof Error ? e.message : String(e)}`);
|
|
16301
|
+
throw e;
|
|
16302
|
+
}
|
|
16303
|
+
const certPem = String(created.cert ?? "");
|
|
16304
|
+
if (!certPem) throw new AuthError(`registerAid: server response missing cert for ${aid}`);
|
|
16305
|
+
identity.cert = certPem;
|
|
16306
|
+
this._assertCertMatchesLocalKeypair(identity);
|
|
16307
|
+
await pendingStore.savePendingCert(pendingHandle, certPem);
|
|
16308
|
+
try {
|
|
16309
|
+
await pendingStore.promotePendingIdentity(pendingHandle, aid);
|
|
16310
|
+
} catch (e) {
|
|
16311
|
+
throw new IdentityConflictError(
|
|
16312
|
+
`AID '${aid}' was created by another process during registration; pending record kept for cleanup.`
|
|
16313
|
+
);
|
|
16314
|
+
}
|
|
16315
|
+
await this._persistIdentity(identity);
|
|
16316
|
+
this._logger.debug(`registerAid exit: elapsed=${Date.now() - tStart}ms aid=${aid}`);
|
|
16317
|
+
return {
|
|
16318
|
+
aid,
|
|
16319
|
+
cert: certPem,
|
|
16320
|
+
private_key_pem: String(identity.private_key_pem ?? ""),
|
|
16321
|
+
public_key_der_b64: String(identity.public_key_der_b64 ?? ""),
|
|
16322
|
+
curve: String(identity.curve ?? "P-256")
|
|
16323
|
+
};
|
|
16324
|
+
} catch (err) {
|
|
16325
|
+
this._logger.debug(`registerAid exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
16326
|
+
throw err;
|
|
16327
|
+
}
|
|
16328
|
+
}
|
|
16329
|
+
_pendingStore() {
|
|
16330
|
+
const store = this._keystore;
|
|
16331
|
+
for (const name of [
|
|
16332
|
+
"pendingIdentityDir",
|
|
16333
|
+
"listPendingIdentityDirs",
|
|
16334
|
+
"savePendingKeyPair",
|
|
16335
|
+
"loadPendingKeyPair",
|
|
16336
|
+
"savePendingCert",
|
|
16337
|
+
"promotePendingIdentity"
|
|
16338
|
+
]) {
|
|
16339
|
+
if (typeof store[name] !== "function") {
|
|
16340
|
+
throw new AuthError(`keystore does not support pending registration: ${name}`);
|
|
16341
|
+
}
|
|
16342
|
+
}
|
|
16343
|
+
return store;
|
|
16344
|
+
}
|
|
16345
|
+
async _tryRecoverPendingRegistration(gatewayUrl, aid) {
|
|
16346
|
+
const pendingStore = this._pendingStore();
|
|
16347
|
+
const handles = await pendingStore.listPendingIdentityDirs(aid);
|
|
16348
|
+
for (const handle of handles) {
|
|
16349
|
+
let keyPair;
|
|
16350
|
+
try {
|
|
16351
|
+
keyPair = await pendingStore.loadPendingKeyPair(handle, aid);
|
|
16352
|
+
} catch (exc) {
|
|
16353
|
+
this._logger.warn(`pending identity keypair load failed; keeping pending for retry: aid=${aid}, pending=${handle}, error=${exc instanceof Error ? exc.message : String(exc)}`);
|
|
16354
|
+
throw exc;
|
|
16355
|
+
}
|
|
16356
|
+
const privateKeyPem = String(keyPair?.private_key_pem ?? "");
|
|
16357
|
+
const publicKeyDerB642 = String(keyPair?.public_key_der_b64 ?? "");
|
|
16358
|
+
if (!privateKeyPem || !publicKeyDerB642) {
|
|
16359
|
+
await pendingStore.discardPendingIdentity?.(handle);
|
|
16360
|
+
continue;
|
|
16361
|
+
}
|
|
16362
|
+
const serverCertPem = await this._downloadRegisteredCert(gatewayUrl, aid);
|
|
16363
|
+
if (!serverCertPem) {
|
|
16364
|
+
this._logger.info(`pending record found but server has no registration; cleaning up: ${handle}`);
|
|
16365
|
+
await pendingStore.discardPendingIdentity?.(handle);
|
|
16366
|
+
return null;
|
|
16367
|
+
}
|
|
16368
|
+
const serverPubB64 = _extractSpkiB64FromPem(serverCertPem);
|
|
16369
|
+
if (serverPubB64 !== publicKeyDerB642) {
|
|
16370
|
+
await pendingStore.discardPendingIdentity?.(handle);
|
|
16371
|
+
throw new IdentityConflictError(
|
|
16372
|
+
`AID '${aid}' has been registered by another party while local pending registration was incomplete; local pending key discarded.`
|
|
16373
|
+
);
|
|
16374
|
+
}
|
|
16375
|
+
const identity = {
|
|
16376
|
+
aid,
|
|
16377
|
+
cert: serverCertPem,
|
|
16378
|
+
private_key_pem: privateKeyPem,
|
|
16379
|
+
public_key_der_b64: publicKeyDerB642,
|
|
16380
|
+
curve: String(keyPair?.curve ?? "P-256")
|
|
16381
|
+
};
|
|
16382
|
+
await pendingStore.savePendingKeyPair(handle, aid, identity);
|
|
16383
|
+
await pendingStore.savePendingCert(handle, serverCertPem);
|
|
16384
|
+
try {
|
|
16385
|
+
await pendingStore.promotePendingIdentity(handle, aid);
|
|
16386
|
+
} catch (e) {
|
|
16387
|
+
throw new IdentityConflictError(
|
|
16388
|
+
`AID '${aid}' was created by another process during recovery; pending record kept for cleanup.`
|
|
16389
|
+
);
|
|
16390
|
+
}
|
|
16391
|
+
await this._persistIdentity(identity);
|
|
16392
|
+
return {
|
|
16393
|
+
aid,
|
|
16394
|
+
cert: serverCertPem,
|
|
16395
|
+
private_key_pem: privateKeyPem,
|
|
16396
|
+
public_key_der_b64: publicKeyDerB642,
|
|
16397
|
+
curve: String(keyPair?.curve ?? "P-256")
|
|
16398
|
+
};
|
|
16399
|
+
}
|
|
16400
|
+
return null;
|
|
16401
|
+
}
|
|
16402
|
+
async _downloadRegisteredCert(gatewayUrl, aid) {
|
|
16403
|
+
const certUrl = _gatewayHttpUrl(gatewayUrl, `/pki/cert/${aid}`);
|
|
16404
|
+
const controller = new AbortController();
|
|
16405
|
+
const timer = setTimeout(() => controller.abort(), 5e3);
|
|
16406
|
+
try {
|
|
16407
|
+
const resp = await fetch(certUrl, { signal: controller.signal });
|
|
16408
|
+
if (!resp.ok) return null;
|
|
16409
|
+
const certPem = await resp.text();
|
|
16410
|
+
if (!certPem || !certPem.includes("BEGIN CERTIFICATE")) return null;
|
|
16411
|
+
return certPem;
|
|
16412
|
+
} catch {
|
|
16413
|
+
return null;
|
|
16414
|
+
} finally {
|
|
16415
|
+
clearTimeout(timer);
|
|
16416
|
+
}
|
|
16417
|
+
}
|
|
16418
|
+
async _createAid(gatewayUrl, identity) {
|
|
16419
|
+
const response = await this._shortRpc(gatewayUrl, "auth.create_aid", {
|
|
16420
|
+
aid: identity.aid,
|
|
16421
|
+
public_key: identity.public_key_der_b64,
|
|
16422
|
+
curve: identity.curve ?? "P-256"
|
|
16423
|
+
});
|
|
16424
|
+
return { cert: response.cert };
|
|
16425
|
+
}
|
|
16426
|
+
_shortRpc(gatewayUrl, method, params) {
|
|
16427
|
+
return new Promise((resolve, reject) => {
|
|
16428
|
+
let ws;
|
|
16429
|
+
try {
|
|
16430
|
+
ws = new WebSocket(gatewayUrl);
|
|
16431
|
+
} catch {
|
|
16432
|
+
reject(new AuthError(`WebSocket \u8FDE\u63A5\u5931\u8D25: ${gatewayUrl}`));
|
|
16433
|
+
return;
|
|
16434
|
+
}
|
|
16435
|
+
let receivedChallenge = false;
|
|
16436
|
+
const timeout = globalThis.setTimeout(() => {
|
|
16437
|
+
try {
|
|
16438
|
+
ws.close();
|
|
16439
|
+
} catch {
|
|
16440
|
+
}
|
|
16441
|
+
reject(new AuthError(`shortRpc \u8D85\u65F6: ${method}`));
|
|
16442
|
+
}, 1e4);
|
|
16443
|
+
ws.onerror = () => {
|
|
16444
|
+
globalThis.clearTimeout(timeout);
|
|
16445
|
+
reject(new AuthError(`WebSocket \u8FDE\u63A5\u9519\u8BEF: ${gatewayUrl}`));
|
|
16446
|
+
};
|
|
16447
|
+
ws.onmessage = (event) => {
|
|
16448
|
+
try {
|
|
16449
|
+
const msg = typeof event.data === "string" ? JSON.parse(event.data) : event.data;
|
|
16450
|
+
if (!receivedChallenge) {
|
|
16451
|
+
receivedChallenge = true;
|
|
16452
|
+
ws.send(JSON.stringify({ jsonrpc: "2.0", id: `pre-${method}`, method, params }));
|
|
16453
|
+
return;
|
|
16454
|
+
}
|
|
16455
|
+
globalThis.clearTimeout(timeout);
|
|
16456
|
+
try {
|
|
16457
|
+
ws.close();
|
|
16458
|
+
} catch {
|
|
16459
|
+
}
|
|
16460
|
+
if (msg.error) {
|
|
16461
|
+
reject(mapRemoteError(msg.error));
|
|
16462
|
+
return;
|
|
16463
|
+
}
|
|
16464
|
+
const result = msg.result;
|
|
16465
|
+
if (!isJsonObject(result)) {
|
|
16466
|
+
reject(new ValidationError(`invalid pre-auth response for ${method}`));
|
|
16467
|
+
return;
|
|
16468
|
+
}
|
|
16469
|
+
if (result.success === false) {
|
|
16470
|
+
reject(new AuthError(String(result.error ?? `${method} failed`)));
|
|
16471
|
+
return;
|
|
16472
|
+
}
|
|
16473
|
+
resolve(result);
|
|
16474
|
+
} catch (e) {
|
|
16475
|
+
globalThis.clearTimeout(timeout);
|
|
16476
|
+
try {
|
|
16477
|
+
ws.close();
|
|
16478
|
+
} catch {
|
|
16479
|
+
}
|
|
16480
|
+
reject(e instanceof Error ? e : new AuthError(String(e)));
|
|
16481
|
+
}
|
|
16482
|
+
};
|
|
16483
|
+
});
|
|
16484
|
+
}
|
|
16485
|
+
_assertCertMatchesLocalKeypair(identity) {
|
|
16486
|
+
const aid = identity.aid ?? "?";
|
|
16487
|
+
const certPem = identity.cert;
|
|
16488
|
+
const localPubB64 = identity.public_key_der_b64;
|
|
16489
|
+
if (!certPem || !localPubB64) {
|
|
16490
|
+
throw new AuthError(`identity for aid ${aid} missing cert or public key`);
|
|
16491
|
+
}
|
|
16492
|
+
const certPubB64 = _extractSpkiB64FromPem(certPem);
|
|
16493
|
+
if (certPubB64 !== localPubB64) {
|
|
16494
|
+
throw new AuthError(`local certificate public key does not match local keypair for aid ${aid}`);
|
|
16495
|
+
}
|
|
16496
|
+
}
|
|
16497
|
+
async _persistIdentity(identity) {
|
|
16498
|
+
const aid = String(identity.aid ?? "");
|
|
16499
|
+
if (!aid) return;
|
|
16500
|
+
const certPem = String(identity.cert ?? "");
|
|
16501
|
+
if (certPem) await this._keystore.saveCert(aid, certPem);
|
|
16502
|
+
}
|
|
16503
|
+
};
|
|
16504
|
+
|
|
16505
|
+
// src/aid-store.ts
|
|
16089
16506
|
function _derReadLength(data, offset) {
|
|
16090
16507
|
if (offset >= data.length) return null;
|
|
16091
16508
|
const first = data[offset];
|
|
@@ -16311,6 +16728,7 @@ var AIDStore = class {
|
|
|
16311
16728
|
__publicField(this, "_encryptionSeed");
|
|
16312
16729
|
__publicField(this, "_keystore");
|
|
16313
16730
|
__publicField(this, "_auth");
|
|
16731
|
+
__publicField(this, "_registerFlow");
|
|
16314
16732
|
__publicField(this, "_crypto");
|
|
16315
16733
|
__publicField(this, "_discovery");
|
|
16316
16734
|
__publicField(this, "_verifySsl");
|
|
@@ -16328,13 +16746,18 @@ var AIDStore = class {
|
|
|
16328
16746
|
this._crypto = new CryptoProvider();
|
|
16329
16747
|
this._discovery = new GatewayDiscovery();
|
|
16330
16748
|
this._auth = new AuthFlow({
|
|
16331
|
-
|
|
16749
|
+
tokenStore: this._keystore,
|
|
16332
16750
|
crypto: this._crypto,
|
|
16333
16751
|
deviceId: this.deviceId,
|
|
16334
16752
|
slotId: this.slotId,
|
|
16335
16753
|
rootCaPem: opts.rootCaPem ?? null,
|
|
16336
16754
|
verifySsl: this._verifySsl
|
|
16337
16755
|
});
|
|
16756
|
+
this._registerFlow = new RegisterFlow({
|
|
16757
|
+
keystore: this._keystore,
|
|
16758
|
+
crypto: this._crypto,
|
|
16759
|
+
verifySsl: this._verifySsl
|
|
16760
|
+
});
|
|
16338
16761
|
}
|
|
16339
16762
|
close() {
|
|
16340
16763
|
const close = this._keystore.close;
|
|
@@ -16453,7 +16876,17 @@ var AIDStore = class {
|
|
|
16453
16876
|
try {
|
|
16454
16877
|
validateRegisterAidName(target);
|
|
16455
16878
|
const gatewayUrl = await this._resolveGateway(target);
|
|
16456
|
-
await this.
|
|
16879
|
+
const result = await this._registerFlow.registerAid(gatewayUrl, target);
|
|
16880
|
+
if (result.cert) {
|
|
16881
|
+
await this._keystore.saveCert(target, result.cert);
|
|
16882
|
+
}
|
|
16883
|
+
if (result.private_key_pem || result.public_key_der_b64) {
|
|
16884
|
+
await this._keystore.saveKeyPair(target, {
|
|
16885
|
+
private_key_pem: result.private_key_pem,
|
|
16886
|
+
public_key_der_b64: result.public_key_der_b64,
|
|
16887
|
+
curve: result.curve
|
|
16888
|
+
});
|
|
16889
|
+
}
|
|
16457
16890
|
return resultOk({ registered: true });
|
|
16458
16891
|
} catch (exc) {
|
|
16459
16892
|
if (exc instanceof IdentityConflictError) {
|
|
@@ -16656,18 +17089,18 @@ var AIDStore = class {
|
|
|
16656
17089
|
return resultErr(PRIVATE_KEY_REQUIRED, `private key required for aid: ${target}`);
|
|
16657
17090
|
}
|
|
16658
17091
|
try {
|
|
16659
|
-
const
|
|
16660
|
-
if (!
|
|
17092
|
+
const aidObj = loaded.data.aid;
|
|
17093
|
+
if (!aidObj.certPem || !aidObj.privateKeyPem) {
|
|
16661
17094
|
return resultErr(PRIVATE_KEY_REQUIRED, `private key required for aid: ${target}`);
|
|
16662
17095
|
}
|
|
16663
17096
|
const gatewayUrl = await this._resolveGateway(target);
|
|
16664
17097
|
const clientNonce = this._crypto.newClientNonce();
|
|
16665
17098
|
const phase1 = await this._auth._shortRpc(gatewayUrl, "auth.aid_login1", {
|
|
16666
17099
|
aid: target,
|
|
16667
|
-
cert:
|
|
17100
|
+
cert: aidObj.certPem,
|
|
16668
17101
|
client_nonce: clientNonce
|
|
16669
17102
|
});
|
|
16670
|
-
const [signature, clientTime] = await this._crypto.signLoginNonce(
|
|
17103
|
+
const [signature, clientTime] = await this._crypto.signLoginNonce(aidObj.privateKeyPem, String(phase1.nonce ?? ""));
|
|
16671
17104
|
const response = await this._auth._shortRpc(gatewayUrl, "auth.renew_cert", {
|
|
16672
17105
|
aid: target,
|
|
16673
17106
|
request_id: String(phase1.request_id ?? ""),
|
|
@@ -16696,8 +17129,8 @@ var AIDStore = class {
|
|
|
16696
17129
|
return resultErr(PRIVATE_KEY_REQUIRED, `private key required for aid: ${target}`);
|
|
16697
17130
|
}
|
|
16698
17131
|
try {
|
|
16699
|
-
const
|
|
16700
|
-
if (!
|
|
17132
|
+
const oldAid = loaded.data.aid;
|
|
17133
|
+
if (!oldAid.certPem || !oldAid.privateKeyPem) {
|
|
16701
17134
|
return resultErr(PRIVATE_KEY_REQUIRED, `private key required for aid: ${target}`);
|
|
16702
17135
|
}
|
|
16703
17136
|
const gatewayUrl = await this._resolveGateway(target);
|
|
@@ -16705,7 +17138,7 @@ var AIDStore = class {
|
|
|
16705
17138
|
const clientNonce = this._crypto.newClientNonce();
|
|
16706
17139
|
const phase1 = await this._auth._shortRpc(gatewayUrl, "auth.aid_login1", {
|
|
16707
17140
|
aid: target,
|
|
16708
|
-
cert:
|
|
17141
|
+
cert: oldAid.certPem,
|
|
16709
17142
|
client_nonce: clientNonce
|
|
16710
17143
|
});
|
|
16711
17144
|
const signPayload = `${String(phase1.nonce ?? "")}${newIdentity.public_key_der_b64}`;
|
|
@@ -16886,6 +17319,7 @@ export {
|
|
|
16886
17319
|
ROOT_CA_PEM,
|
|
16887
17320
|
RPCTransport,
|
|
16888
17321
|
RateLimitError,
|
|
17322
|
+
RegisterFlow,
|
|
16889
17323
|
STATE_PREFIX,
|
|
16890
17324
|
SeedMigrationError,
|
|
16891
17325
|
SerializationError,
|