@agentunion/fastaun 0.4.5 → 0.4.6
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 +26 -0
- package/_packed_docs/CHANGELOG.md +26 -0
- package/_packed_docs/INDEX.md +2 -2
- package/_packed_docs/KITE_DOCS_GUIDE.md +1 -1
- package/_packed_docs/agent.md//350/277/234/347/250/213agent.md/347/274/223/345/255/230/344/270/216etag/351/200/217/344/274/240/346/226/271/346/241/210.md +73 -84
- package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +15 -14
- package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +2 -2
- package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +22 -5
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +42 -26
- package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +1 -1
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +61 -35
- package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +3 -3
- package/_packed_docs/sdk/09-message-rpc-manual.md +6 -6
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +6 -4
- package/_packed_docs/sdk/INDEX.md +2 -2
- package/_packed_docs/sdk/README.md +3 -3
- package/dist/agent-md.d.ts +101 -0
- package/dist/agent-md.js +778 -0
- package/dist/agent-md.js.map +1 -0
- package/dist/aid-store.d.ts +6 -39
- package/dist/aid-store.js +52 -142
- package/dist/aid-store.js.map +1 -1
- package/dist/auth.js +1 -1
- package/dist/auth.js.map +1 -1
- package/dist/client.d.ts +2 -62
- package/dist/client.js +140 -825
- package/dist/client.js.map +1 -1
- package/dist/crypto.d.ts +1 -1
- package/dist/crypto.js +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/keystore/index.d.ts +6 -2
- package/dist/keystore/local-identity-store.d.ts +70 -0
- package/dist/keystore/local-identity-store.js +525 -0
- package/dist/keystore/local-identity-store.js.map +1 -0
- package/dist/keystore/local-token-store.d.ts +68 -0
- package/dist/keystore/local-token-store.js +368 -0
- package/dist/keystore/local-token-store.js.map +1 -0
- package/dist/register-flow.d.ts +12 -4
- package/dist/register-flow.js +70 -3
- package/dist/register-flow.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/crypto.d.ts
CHANGED
|
@@ -36,6 +36,6 @@ export declare class CryptoProvider {
|
|
|
36
36
|
/**
|
|
37
37
|
* 统一的证书 SHA-256 指纹计算(对齐 Python certificate_sha256_fingerprint)。
|
|
38
38
|
* 返回 "sha256:{hex}" 格式的指纹。
|
|
39
|
-
* e2ee.ts
|
|
39
|
+
* e2ee.ts 与本地存储实现共用此函数,避免两套实现不一致。
|
|
40
40
|
*/
|
|
41
41
|
export declare function certificateSha256Fingerprint(certPem: string | Buffer): string;
|
package/dist/crypto.js
CHANGED
|
@@ -54,7 +54,7 @@ export class CryptoProvider {
|
|
|
54
54
|
/**
|
|
55
55
|
* 统一的证书 SHA-256 指纹计算(对齐 Python certificate_sha256_fingerprint)。
|
|
56
56
|
* 返回 "sha256:{hex}" 格式的指纹。
|
|
57
|
-
* e2ee.ts
|
|
57
|
+
* e2ee.ts 与本地存储实现共用此函数,避免两套实现不一致。
|
|
58
58
|
*/
|
|
59
59
|
export function certificateSha256Fingerprint(certPem) {
|
|
60
60
|
const raw = Buffer.isBuffer(certPem) ? certPem : Buffer.from(certPem, 'utf-8');
|
package/dist/index.d.ts
CHANGED
|
@@ -13,8 +13,9 @@ export { AUNError, ConnectionError, TimeoutError, AuthError, PermissionError, Va
|
|
|
13
13
|
export { EventDispatcher, Subscription, type EventHandler } from './events.js';
|
|
14
14
|
export { ConnectionState, type JsonValue, type JsonObject, type RpcParams, type RpcResult, type RpcErrorObject, type RpcMessage, type GatewayEntry, type GatewayDiscoveryDocument, type KeyPairRecord, type MetadataRecord, type IdentityRecord, type SecretRecord, type Message, type SendResult, type AckResult, type PullResult, isJsonObject, } from './types.js';
|
|
15
15
|
export { CryptoProvider, type IdentityKeyPair } from './crypto.js';
|
|
16
|
-
export type { KeyStore } from './keystore/index.js';
|
|
17
|
-
export {
|
|
16
|
+
export type { KeyStore, TokenStore } from './keystore/index.js';
|
|
17
|
+
export { LocalIdentityStore } from './keystore/local-identity-store.js';
|
|
18
|
+
export { LocalTokenStore } from './keystore/local-token-store.js';
|
|
18
19
|
export type { SecretStore } from './secret-store/index.js';
|
|
19
20
|
export { createDefaultSecretStore } from './secret-store/index.js';
|
|
20
21
|
export { FileSecretStore, SeedMigrationError, type SeedChangeResult } from './secret-store/file-store.js';
|
package/dist/index.js
CHANGED
|
@@ -19,7 +19,8 @@ export { EventDispatcher, Subscription } from './events.js';
|
|
|
19
19
|
export { ConnectionState, isJsonObject, } from './types.js';
|
|
20
20
|
// ── 密码学 ───────────────────────────────────────────────────
|
|
21
21
|
export { CryptoProvider } from './crypto.js';
|
|
22
|
-
export {
|
|
22
|
+
export { LocalIdentityStore } from './keystore/local-identity-store.js';
|
|
23
|
+
export { LocalTokenStore } from './keystore/local-token-store.js';
|
|
23
24
|
export { createDefaultSecretStore } from './secret-store/index.js';
|
|
24
25
|
export { FileSecretStore, SeedMigrationError } from './secret-store/file-store.js';
|
|
25
26
|
// ── 传输层 ───────────────────────────────────────────────────
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,4DAA4D;AAC5D,OAAO,EAAE,SAAS,EAA0B,MAAM,aAAa,CAAC;AAChE,OAAO,EAAE,GAAG,EAAqB,MAAM,UAAU,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAkC,MAAM,gBAAgB,CAAC;AAC1E,OAAO,EAA+B,QAAQ,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE/E,8DAA8D;AAC9D,OAAO,EAAE,WAAW,EAAkB,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAExF,4DAA4D;AAC5D,OAAO,EACL,QAAQ,EACR,eAAe,EACf,YAAY,EACZ,SAAS,EACT,eAAe,EACf,eAAe,EACf,aAAa,EACb,cAAc,EACd,UAAU,EACV,kBAAkB,EAClB,YAAY,EACZ,UAAU,EACV,kBAAkB,EAClB,eAAe,EACf,SAAS,EACT,sBAAsB,EACtB,+BAA+B,EAC/B,uBAAuB,EACvB,2BAA2B,EAC3B,uBAAuB,EACvB,iBAAiB,EACjB,oBAAoB,EACpB,cAAc,GACf,MAAM,aAAa,CAAC;AAErB,8DAA8D;AAC9D,OAAO,EAAE,eAAe,EAAE,YAAY,EAAqB,MAAM,aAAa,CAAC;AAE/E,8DAA8D;AAC9D,OAAO,EACL,eAAe,EAiBf,YAAY,GACb,MAAM,YAAY,CAAC;AAEpB,6DAA6D;AAC7D,OAAO,EAAE,cAAc,EAAwB,MAAM,aAAa,CAAC;AAInE,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,4DAA4D;AAC5D,OAAO,EAAE,SAAS,EAA0B,MAAM,aAAa,CAAC;AAChE,OAAO,EAAE,GAAG,EAAqB,MAAM,UAAU,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAkC,MAAM,gBAAgB,CAAC;AAC1E,OAAO,EAA+B,QAAQ,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE/E,8DAA8D;AAC9D,OAAO,EAAE,WAAW,EAAkB,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAExF,4DAA4D;AAC5D,OAAO,EACL,QAAQ,EACR,eAAe,EACf,YAAY,EACZ,SAAS,EACT,eAAe,EACf,eAAe,EACf,aAAa,EACb,cAAc,EACd,UAAU,EACV,kBAAkB,EAClB,YAAY,EACZ,UAAU,EACV,kBAAkB,EAClB,eAAe,EACf,SAAS,EACT,sBAAsB,EACtB,+BAA+B,EAC/B,uBAAuB,EACvB,2BAA2B,EAC3B,uBAAuB,EACvB,iBAAiB,EACjB,oBAAoB,EACpB,cAAc,GACf,MAAM,aAAa,CAAC;AAErB,8DAA8D;AAC9D,OAAO,EAAE,eAAe,EAAE,YAAY,EAAqB,MAAM,aAAa,CAAC;AAE/E,8DAA8D;AAC9D,OAAO,EACL,eAAe,EAiBf,YAAY,GACb,MAAM,YAAY,CAAC;AAEpB,6DAA6D;AAC7D,OAAO,EAAE,cAAc,EAAwB,MAAM,aAAa,CAAC;AAInE,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAIlE,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAyB,MAAM,8BAA8B,CAAC;AAE1G,6DAA6D;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,8DAA8D;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElD,4DAA4D;AAC5D,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,YAAY,EAAuB,MAAM,oBAAoB,CAAC;AAEvE,gEAAgE;AAChE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAG1D,gEAAgE;AAChE,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,cAAc,GACf,MAAM,oBAAoB,CAAC;AAQ5B,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAE9D,OAAO,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/keystore/index.d.ts
CHANGED
|
@@ -62,6 +62,12 @@ export interface TokenStore {
|
|
|
62
62
|
}
|
|
63
63
|
/** 私钥/完整身份存储接口,仅 AIDStore / RegisterFlow 持有。 */
|
|
64
64
|
export interface KeyStore {
|
|
65
|
+
/** 加载证书 */
|
|
66
|
+
loadCert(aid: string, certFingerprint?: string): string | null;
|
|
67
|
+
/** 保存证书 */
|
|
68
|
+
saveCert(aid: string, certPem: string, certFingerprint?: string, opts?: {
|
|
69
|
+
makeActive?: boolean;
|
|
70
|
+
}): void;
|
|
65
71
|
/** 加载密钥对 */
|
|
66
72
|
loadKeyPair(aid: string): KeyPairRecord | null;
|
|
67
73
|
/** 保存密钥对 */
|
|
@@ -89,5 +95,3 @@ export interface KeyStore {
|
|
|
89
95
|
/** 列出所有已存储的 AID */
|
|
90
96
|
listIdentities?(): string[];
|
|
91
97
|
}
|
|
92
|
-
/** 物理实现通常同时实现 TokenStore 与 KeyStore;注册流程显式要求组合类型。 */
|
|
93
|
-
export type FullKeyStore = TokenStore & KeyStore;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LocalIdentityStore — 基于文件系统 + SQLite 的 KeyStore 实现(含私钥操作)。
|
|
3
|
+
* AIDStore / RegisterFlow 持有此类型。
|
|
4
|
+
*/
|
|
5
|
+
import type { KeyStore } from './index.js';
|
|
6
|
+
import type { SecretStore } from '../secret-store/index.js';
|
|
7
|
+
import type { ModuleLogger } from '../logger.js';
|
|
8
|
+
import { type SeedChangeResult } from '../secret-store/file-store.js';
|
|
9
|
+
import { type IdentityRecord, type KeyPairRecord, type MetadataRecord } from '../types.js';
|
|
10
|
+
export declare class LocalIdentityStore implements KeyStore {
|
|
11
|
+
private _root;
|
|
12
|
+
private _aidsRoot;
|
|
13
|
+
private _secretStore;
|
|
14
|
+
private _aidDBs;
|
|
15
|
+
readonly deviceId: string;
|
|
16
|
+
private _logger;
|
|
17
|
+
constructor(root?: string, opts?: {
|
|
18
|
+
secretStore?: SecretStore;
|
|
19
|
+
encryptionSeed?: string;
|
|
20
|
+
logger?: ModuleLogger;
|
|
21
|
+
secretStoreLogger?: ModuleLogger;
|
|
22
|
+
});
|
|
23
|
+
close(): void;
|
|
24
|
+
static ChangeSeed(root: string, oldSeed: string, newSeed: string): SeedChangeResult;
|
|
25
|
+
changeSeed(oldSeed: string, newSeed: string): SeedChangeResult;
|
|
26
|
+
private _prepareRoot;
|
|
27
|
+
private _getDB;
|
|
28
|
+
loadKeyPair(aid: string): KeyPairRecord | null;
|
|
29
|
+
saveKeyPair(aid: string, keyPair: KeyPairRecord): void;
|
|
30
|
+
private _saveKeyPairAtPath;
|
|
31
|
+
private _restoreKeyPair;
|
|
32
|
+
loadCert(aid: string, certFingerprint?: string): string | null;
|
|
33
|
+
saveCert(aid: string, certPem: string, certFingerprint?: string, opts?: {
|
|
34
|
+
makeActive?: boolean;
|
|
35
|
+
}): void;
|
|
36
|
+
private _normalizeCertFingerprint;
|
|
37
|
+
loadIdentity(aid: string): IdentityRecord | null;
|
|
38
|
+
saveIdentity(aid: string, identity: IdentityRecord): void;
|
|
39
|
+
loadAnyIdentity(): IdentityRecord | null;
|
|
40
|
+
listIdentities(): string[];
|
|
41
|
+
loadMetadata(aid: string): Record<string, unknown> | null;
|
|
42
|
+
saveMetadata(aid: string, metadata: Record<string, unknown>): void;
|
|
43
|
+
loadInstanceState(aid: string, deviceId: string, slotId?: string): MetadataRecord | null;
|
|
44
|
+
saveInstanceState(aid: string, deviceId: string, slotId: string, state: MetadataRecord): void;
|
|
45
|
+
saveSeq(aid: string, deviceId: string, slotId: string, namespace: string, contiguousSeq: number): void;
|
|
46
|
+
loadSeq(aid: string, deviceId: string, slotId: string, namespace: string): number;
|
|
47
|
+
loadAllSeqs(aid: string, deviceId: string, slotId: string): Record<string, number>;
|
|
48
|
+
trustRootDir(): string;
|
|
49
|
+
trustRootBundlePath(): string;
|
|
50
|
+
saveTrustRoots(trustList: Record<string, unknown>, rootCerts: Array<{
|
|
51
|
+
id?: string;
|
|
52
|
+
cert_pem: string;
|
|
53
|
+
fingerprint_sha256?: string;
|
|
54
|
+
}>): string;
|
|
55
|
+
saveIssuerRootCert(issuer: string, certPem: string, fingerprintSha256?: string): [string, string];
|
|
56
|
+
private _pemFingerprint;
|
|
57
|
+
pendingIdentityDir(aid: string): string;
|
|
58
|
+
listPendingIdentityDirs(aid: string): string[];
|
|
59
|
+
savePendingKeyPair(pendingDir: string, aid: string, keyPair: KeyPairRecord): void;
|
|
60
|
+
loadPendingKeyPair(pendingDir: string, aid: string): KeyPairRecord | null;
|
|
61
|
+
savePendingCert(pendingDir: string, certPem: string): void;
|
|
62
|
+
promotePendingIdentity(pendingDir: string, aid: string): string;
|
|
63
|
+
discardPendingIdentity(pendingDir: string): void;
|
|
64
|
+
cleanupPendingDirs(maxAgeMs?: number): number;
|
|
65
|
+
private _ensurePendingKeyPairProtected;
|
|
66
|
+
private _pendingRoot;
|
|
67
|
+
private _keyPairPath;
|
|
68
|
+
private _certPath;
|
|
69
|
+
private _certVersionPath;
|
|
70
|
+
}
|
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LocalIdentityStore — 基于文件系统 + SQLite 的 KeyStore 实现(含私钥操作)。
|
|
3
|
+
* AIDStore / RegisterFlow 持有此类型。
|
|
4
|
+
*/
|
|
5
|
+
import * as crypto from 'node:crypto';
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, chmodSync, renameSync, unlinkSync, rmSync as fsRmSync, renameSync as fsRenameSync, statSync, } from 'node:fs';
|
|
7
|
+
import { join, dirname } from 'node:path';
|
|
8
|
+
import { homedir } from 'node:os';
|
|
9
|
+
import { AIDDatabase } from './aid-db.js';
|
|
10
|
+
import { getDeviceId } from '../config.js';
|
|
11
|
+
import { certificateSha256Fingerprint } from '../crypto.js';
|
|
12
|
+
import { createDefaultSecretStore } from '../secret-store/index.js';
|
|
13
|
+
import { FileSecretStore } from '../secret-store/file-store.js';
|
|
14
|
+
import { isJsonObject, } from '../types.js';
|
|
15
|
+
const _noopLogger = { error: () => { }, warn: () => { }, info: () => { }, debug: () => { } };
|
|
16
|
+
function secureFilePermissions(path) {
|
|
17
|
+
if (process.platform !== 'win32') {
|
|
18
|
+
try {
|
|
19
|
+
chmodSync(path, 0o600);
|
|
20
|
+
}
|
|
21
|
+
catch { /* ignore */ }
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function replaceFileSync(tmpPath, targetPath) {
|
|
25
|
+
try {
|
|
26
|
+
renameSync(tmpPath, targetPath);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
catch (renameErr) {
|
|
30
|
+
try {
|
|
31
|
+
unlinkSync(targetPath);
|
|
32
|
+
}
|
|
33
|
+
catch (unlinkErr) {
|
|
34
|
+
if (unlinkErr.code !== 'ENOENT') {
|
|
35
|
+
throw new Error(`replace target cleanup failed: ${unlinkErr instanceof Error ? unlinkErr.message : String(unlinkErr)}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
renameSync(tmpPath, targetPath);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function safeAid(aid) {
|
|
42
|
+
return aid.replace(/[/\\:]/g, '_');
|
|
43
|
+
}
|
|
44
|
+
export class LocalIdentityStore {
|
|
45
|
+
_root;
|
|
46
|
+
_aidsRoot;
|
|
47
|
+
_secretStore;
|
|
48
|
+
_aidDBs = new Map();
|
|
49
|
+
deviceId;
|
|
50
|
+
_logger;
|
|
51
|
+
constructor(root, opts) {
|
|
52
|
+
this._logger = opts?.logger ?? _noopLogger;
|
|
53
|
+
const preferred = root ?? join(homedir(), '.aun');
|
|
54
|
+
const fallback = join(process.cwd(), '.aun');
|
|
55
|
+
this._root = this._prepareRoot(preferred, fallback);
|
|
56
|
+
this._secretStore = opts?.secretStore ?? createDefaultSecretStore(this._root, opts?.encryptionSeed, undefined, { logger: opts?.secretStoreLogger ?? this._logger });
|
|
57
|
+
this._aidsRoot = join(this._root, 'AIDs');
|
|
58
|
+
mkdirSync(this._aidsRoot, { recursive: true });
|
|
59
|
+
this.deviceId = getDeviceId(this._root);
|
|
60
|
+
}
|
|
61
|
+
close() {
|
|
62
|
+
for (const db of this._aidDBs.values())
|
|
63
|
+
db.close();
|
|
64
|
+
this._aidDBs.clear();
|
|
65
|
+
}
|
|
66
|
+
static ChangeSeed(root, oldSeed, newSeed) {
|
|
67
|
+
return FileSecretStore.changeSeed(root, oldSeed, newSeed);
|
|
68
|
+
}
|
|
69
|
+
changeSeed(oldSeed, newSeed) {
|
|
70
|
+
this.close();
|
|
71
|
+
const result = FileSecretStore.changeSeed(this._root, oldSeed, newSeed, { logger: this._logger });
|
|
72
|
+
this._secretStore = createDefaultSecretStore(this._root, newSeed, undefined, { logger: this._logger });
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
_prepareRoot(preferred, fallback) {
|
|
76
|
+
try {
|
|
77
|
+
mkdirSync(preferred, { recursive: true });
|
|
78
|
+
return preferred;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
this._logger.warn(`preferred path ${preferred} unavailable, falling back to ${fallback}`);
|
|
82
|
+
mkdirSync(fallback, { recursive: true });
|
|
83
|
+
return fallback;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
_getDB(aid) {
|
|
87
|
+
const safe = safeAid(aid);
|
|
88
|
+
let db = this._aidDBs.get(safe);
|
|
89
|
+
if (!db) {
|
|
90
|
+
const dbPath = join(this._aidsRoot, safe, 'aun.db');
|
|
91
|
+
try {
|
|
92
|
+
db = new AIDDatabase(dbPath, this._secretStore, safe, this._logger);
|
|
93
|
+
}
|
|
94
|
+
catch (exc) {
|
|
95
|
+
this._logger.warn(`database corrupted, backing up and rebuilding: aid=${aid} err=${exc instanceof Error ? exc.message : String(exc)}`);
|
|
96
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
97
|
+
const bakPath = dbPath + `.corrupt_${ts}.bak`;
|
|
98
|
+
try {
|
|
99
|
+
renameSync(dbPath, bakPath);
|
|
100
|
+
}
|
|
101
|
+
catch (renameErr) {
|
|
102
|
+
this._logger.warn(`backup rename failed: ${renameErr instanceof Error ? renameErr.message : String(renameErr)}`);
|
|
103
|
+
}
|
|
104
|
+
for (const suffix of ['-wal', '-shm', '-journal']) {
|
|
105
|
+
try {
|
|
106
|
+
unlinkSync(dbPath + suffix);
|
|
107
|
+
}
|
|
108
|
+
catch { /* ignore */ }
|
|
109
|
+
}
|
|
110
|
+
db = new AIDDatabase(dbPath, this._secretStore, safe, this._logger);
|
|
111
|
+
}
|
|
112
|
+
this._aidDBs.set(safe, db);
|
|
113
|
+
}
|
|
114
|
+
return db;
|
|
115
|
+
}
|
|
116
|
+
// ── KeyPair ──────────────────────────────────────────────
|
|
117
|
+
loadKeyPair(aid) {
|
|
118
|
+
const path = this._keyPairPath(aid);
|
|
119
|
+
if (!existsSync(path))
|
|
120
|
+
return null;
|
|
121
|
+
let raw;
|
|
122
|
+
try {
|
|
123
|
+
raw = JSON.parse(readFileSync(path, 'utf-8'));
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
this._logger.warn('key.json read or parse failed, treating as non-existent');
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
return this._restoreKeyPair(aid, raw, path);
|
|
130
|
+
}
|
|
131
|
+
saveKeyPair(aid, keyPair) {
|
|
132
|
+
this._saveKeyPairAtPath(aid, this._keyPairPath(aid), keyPair);
|
|
133
|
+
}
|
|
134
|
+
_saveKeyPairAtPath(aid, path, keyPair) {
|
|
135
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
136
|
+
const protected_ = JSON.parse(JSON.stringify(keyPair));
|
|
137
|
+
const pem = protected_.private_key_pem;
|
|
138
|
+
if (typeof pem === 'string' && pem) {
|
|
139
|
+
delete protected_.private_key_pem;
|
|
140
|
+
const rec = this._secretStore.protect(safeAid(aid), 'identity/private_key', Buffer.from(pem, 'utf-8'));
|
|
141
|
+
protected_.private_key_protection = rec;
|
|
142
|
+
}
|
|
143
|
+
const tmpPath = `${path}.tmp-${process.pid}-${Date.now()}-${crypto.randomBytes(4).toString('hex')}`;
|
|
144
|
+
writeFileSync(tmpPath, JSON.stringify(protected_, null, 2), { mode: 0o600 });
|
|
145
|
+
secureFilePermissions(tmpPath);
|
|
146
|
+
try {
|
|
147
|
+
replaceFileSync(tmpPath, path);
|
|
148
|
+
}
|
|
149
|
+
catch (exc) {
|
|
150
|
+
try {
|
|
151
|
+
unlinkSync(tmpPath);
|
|
152
|
+
}
|
|
153
|
+
catch { /* ignore */ }
|
|
154
|
+
throw exc;
|
|
155
|
+
}
|
|
156
|
+
secureFilePermissions(path);
|
|
157
|
+
}
|
|
158
|
+
_restoreKeyPair(aid, kp, persistPath) {
|
|
159
|
+
const out = JSON.parse(JSON.stringify(kp));
|
|
160
|
+
const rec = out.private_key_protection;
|
|
161
|
+
if (isJsonObject(rec)) {
|
|
162
|
+
const plain = this._secretStore.reveal(safeAid(aid), 'identity/private_key', rec);
|
|
163
|
+
if (!plain)
|
|
164
|
+
throw new Error(`private key decrypt failed for aid ${aid}: seed_password mismatch or key.json corrupted`);
|
|
165
|
+
out.private_key_pem = plain.toString('utf-8');
|
|
166
|
+
return out;
|
|
167
|
+
}
|
|
168
|
+
if (persistPath && typeof out.private_key_pem === 'string' && out.private_key_pem) {
|
|
169
|
+
this._saveKeyPairAtPath(aid, persistPath, out);
|
|
170
|
+
}
|
|
171
|
+
return out;
|
|
172
|
+
}
|
|
173
|
+
// ── Cert ─────────────────────────────────────────────────
|
|
174
|
+
loadCert(aid, certFingerprint) {
|
|
175
|
+
try {
|
|
176
|
+
const norm = this._normalizeCertFingerprint(certFingerprint);
|
|
177
|
+
if (norm) {
|
|
178
|
+
const vp = this._certVersionPath(aid, norm);
|
|
179
|
+
if (existsSync(vp))
|
|
180
|
+
return readFileSync(vp, 'utf-8');
|
|
181
|
+
const active = this._certPath(aid);
|
|
182
|
+
if (existsSync(active)) {
|
|
183
|
+
const certPem = readFileSync(active, 'utf-8');
|
|
184
|
+
if (certificateSha256Fingerprint(certPem) === norm)
|
|
185
|
+
return certPem;
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
const path = this._certPath(aid);
|
|
190
|
+
return existsSync(path) ? readFileSync(path, 'utf-8') : null;
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
this._logger.warn('cert.pem read failed, treating as non-existent');
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
saveCert(aid, certPem, certFingerprint, opts) {
|
|
198
|
+
const norm = this._normalizeCertFingerprint(certFingerprint);
|
|
199
|
+
if (norm) {
|
|
200
|
+
const vp = this._certVersionPath(aid, norm);
|
|
201
|
+
mkdirSync(dirname(vp), { recursive: true });
|
|
202
|
+
writeFileSync(vp, certPem);
|
|
203
|
+
if (!opts?.makeActive)
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const path = this._certPath(aid);
|
|
207
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
208
|
+
writeFileSync(path, certPem);
|
|
209
|
+
}
|
|
210
|
+
_normalizeCertFingerprint(fp) {
|
|
211
|
+
const v = String(fp ?? '').trim().toLowerCase();
|
|
212
|
+
if (!v.startsWith('sha256:') || v.length !== 71)
|
|
213
|
+
return '';
|
|
214
|
+
if (/[^0-9a-f]/.test(v.slice(7)))
|
|
215
|
+
return '';
|
|
216
|
+
return v;
|
|
217
|
+
}
|
|
218
|
+
// ── Identity ─────────────────────────────────────────────
|
|
219
|
+
loadIdentity(aid) {
|
|
220
|
+
const identityDir = join(this._aidsRoot, safeAid(aid));
|
|
221
|
+
if (!existsSync(identityDir))
|
|
222
|
+
return null;
|
|
223
|
+
const kp = this.loadKeyPair(aid);
|
|
224
|
+
const cert = this.loadCert(aid);
|
|
225
|
+
const db = this._getDB(aid);
|
|
226
|
+
const kv = db.getAllMetadata();
|
|
227
|
+
const hasMeta = Object.keys(kv).length > 0;
|
|
228
|
+
if (!kp && !cert && !hasMeta)
|
|
229
|
+
return null;
|
|
230
|
+
const identity = {};
|
|
231
|
+
for (const [k, v] of Object.entries(kv)) {
|
|
232
|
+
try {
|
|
233
|
+
identity[k] = JSON.parse(v);
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
identity[k] = v;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (kp)
|
|
240
|
+
Object.assign(identity, kp);
|
|
241
|
+
if (cert) {
|
|
242
|
+
const localPubB64 = kp?.public_key_der_b64;
|
|
243
|
+
if (typeof localPubB64 === 'string' && localPubB64) {
|
|
244
|
+
try {
|
|
245
|
+
const x = new crypto.X509Certificate(cert);
|
|
246
|
+
const certPubDer = x.publicKey.export({ type: 'spki', format: 'der' });
|
|
247
|
+
const localPubDer = Buffer.from(localPubB64, 'base64');
|
|
248
|
+
if (!certPubDer.equals(localPubDer)) {
|
|
249
|
+
this._logger.error('key.json public key does not match cert.pem public key, discarding cert');
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
identity.cert = cert;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
identity.cert = cert;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
identity.cert = cert;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return identity;
|
|
264
|
+
}
|
|
265
|
+
saveIdentity(aid, identity) {
|
|
266
|
+
const kp = {};
|
|
267
|
+
for (const k of ['private_key_pem', 'public_key_der_b64', 'curve']) {
|
|
268
|
+
if (k in identity)
|
|
269
|
+
kp[k] = identity[k];
|
|
270
|
+
}
|
|
271
|
+
if (Object.keys(kp).length > 0)
|
|
272
|
+
this.saveKeyPair(aid, kp);
|
|
273
|
+
if (typeof identity.cert === 'string' && identity.cert)
|
|
274
|
+
this.saveCert(aid, identity.cert);
|
|
275
|
+
const db = this._getDB(aid);
|
|
276
|
+
const skip = new Set(['private_key_pem', 'public_key_der_b64', 'curve', 'cert']);
|
|
277
|
+
for (const [k, v] of Object.entries(identity)) {
|
|
278
|
+
if (skip.has(k))
|
|
279
|
+
continue;
|
|
280
|
+
db.setMetadata(k, JSON.stringify(v));
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
loadAnyIdentity() {
|
|
284
|
+
if (!existsSync(this._aidsRoot))
|
|
285
|
+
return null;
|
|
286
|
+
for (const entry of readdirSync(this._aidsRoot, { withFileTypes: true })) {
|
|
287
|
+
if (!entry.isDirectory() || entry.name.startsWith('_'))
|
|
288
|
+
continue;
|
|
289
|
+
const identity = this.loadIdentity(entry.name);
|
|
290
|
+
if (identity)
|
|
291
|
+
return identity;
|
|
292
|
+
}
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
listIdentities() {
|
|
296
|
+
if (!existsSync(this._aidsRoot))
|
|
297
|
+
return [];
|
|
298
|
+
const aids = [];
|
|
299
|
+
for (const entry of readdirSync(this._aidsRoot, { withFileTypes: true })) {
|
|
300
|
+
if (!entry.isDirectory() || entry.name.startsWith('_'))
|
|
301
|
+
continue;
|
|
302
|
+
aids.push(entry.name);
|
|
303
|
+
}
|
|
304
|
+
return aids;
|
|
305
|
+
}
|
|
306
|
+
loadMetadata(aid) {
|
|
307
|
+
try {
|
|
308
|
+
const dbPath = join(this._aidsRoot, safeAid(aid), 'aun.db');
|
|
309
|
+
if (!existsSync(dbPath))
|
|
310
|
+
return null;
|
|
311
|
+
const kv = this._getDB(aid).getAllMetadata();
|
|
312
|
+
if (Object.keys(kv).length === 0)
|
|
313
|
+
return null;
|
|
314
|
+
const result = {};
|
|
315
|
+
for (const [k, v] of Object.entries(kv)) {
|
|
316
|
+
try {
|
|
317
|
+
result[k] = JSON.parse(v);
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
result[k] = v;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return result;
|
|
324
|
+
}
|
|
325
|
+
catch {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
saveMetadata(aid, metadata) {
|
|
330
|
+
const db = this._getDB(aid);
|
|
331
|
+
for (const [k, v] of Object.entries(metadata)) {
|
|
332
|
+
if (v === undefined) {
|
|
333
|
+
db.deleteMetadata(k);
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
db.setMetadata(k, JSON.stringify(v));
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
// ── Instance State ───────────────────────────────────────
|
|
340
|
+
loadInstanceState(aid, deviceId, slotId = '') {
|
|
341
|
+
return this._getDB(aid).loadInstanceState(deviceId, slotId);
|
|
342
|
+
}
|
|
343
|
+
saveInstanceState(aid, deviceId, slotId, state) {
|
|
344
|
+
this._getDB(aid).saveInstanceState(deviceId, slotId, state);
|
|
345
|
+
}
|
|
346
|
+
// ── Seq Tracker ───────────────────────────────────────────
|
|
347
|
+
saveSeq(aid, deviceId, slotId, namespace, contiguousSeq) {
|
|
348
|
+
this._getDB(aid).saveSeq(deviceId, slotId, namespace, contiguousSeq);
|
|
349
|
+
}
|
|
350
|
+
loadSeq(aid, deviceId, slotId, namespace) {
|
|
351
|
+
return this._getDB(aid).loadSeq(deviceId, slotId, namespace);
|
|
352
|
+
}
|
|
353
|
+
loadAllSeqs(aid, deviceId, slotId) {
|
|
354
|
+
return this._getDB(aid).loadAllSeqs(deviceId, slotId);
|
|
355
|
+
}
|
|
356
|
+
// ── 信任根管理 ─────────────────────────────────────────────
|
|
357
|
+
trustRootDir() {
|
|
358
|
+
const dir = join(this._root, 'CA', 'root');
|
|
359
|
+
mkdirSync(dir, { recursive: true });
|
|
360
|
+
return dir;
|
|
361
|
+
}
|
|
362
|
+
trustRootBundlePath() {
|
|
363
|
+
return join(this.trustRootDir(), 'trust-roots.pem');
|
|
364
|
+
}
|
|
365
|
+
saveTrustRoots(trustList, rootCerts) {
|
|
366
|
+
const dir = this.trustRootDir();
|
|
367
|
+
for (let i = 0; i < rootCerts.length; i++) {
|
|
368
|
+
const item = rootCerts[i];
|
|
369
|
+
const certId = item.id || item.fingerprint_sha256 || `root-${i + 1}`;
|
|
370
|
+
writeFileSync(join(dir, `${certId.replace(/[^A-Za-z0-9_.-]+/g, '_').slice(0, 120)}.crt`), item.cert_pem, 'utf-8');
|
|
371
|
+
}
|
|
372
|
+
const bundlePath = this.trustRootBundlePath();
|
|
373
|
+
writeFileSync(bundlePath, rootCerts.map(i => i.cert_pem.trim()).join('\n') + '\n', 'utf-8');
|
|
374
|
+
writeFileSync(join(dir, 'trust-roots.json'), JSON.stringify(trustList, null, 2), 'utf-8');
|
|
375
|
+
return bundlePath;
|
|
376
|
+
}
|
|
377
|
+
saveIssuerRootCert(issuer, certPem, fingerprintSha256 = '') {
|
|
378
|
+
const dir = this.trustRootDir();
|
|
379
|
+
const issuersDir = join(dir, 'issuers');
|
|
380
|
+
mkdirSync(issuersDir, { recursive: true });
|
|
381
|
+
const certPath = join(issuersDir, `${(issuer || 'issuer').replace(/[^A-Za-z0-9_.-]+/g, '_').slice(0, 120)}.root.crt`);
|
|
382
|
+
const normalizedPem = certPem.trim() + '\n';
|
|
383
|
+
writeFileSync(certPath, normalizedPem, 'utf-8');
|
|
384
|
+
const bundlePath = this.trustRootBundlePath();
|
|
385
|
+
const existingPems = new Map();
|
|
386
|
+
try {
|
|
387
|
+
for (const pem of readFileSync(bundlePath, 'utf-8').split(/(?<=-----END CERTIFICATE-----)\s*/).map(s => s.trim()).filter(s => s.startsWith('-----BEGIN CERTIFICATE-----'))) {
|
|
388
|
+
existingPems.set(this._pemFingerprint(pem), pem);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
catch { /* ignore */ }
|
|
392
|
+
const newFp = fingerprintSha256 ? fingerprintSha256.toLowerCase().replace(/^sha256:/, '') : this._pemFingerprint(normalizedPem);
|
|
393
|
+
existingPems.set(newFp, normalizedPem);
|
|
394
|
+
writeFileSync(bundlePath, Array.from(existingPems.values()).map(p => p.trim()).join('\n') + '\n', 'utf-8');
|
|
395
|
+
return [certPath, bundlePath];
|
|
396
|
+
}
|
|
397
|
+
_pemFingerprint(pem) {
|
|
398
|
+
try {
|
|
399
|
+
const der = pem.replace(/-----[A-Z ]+-----/g, '').replace(/\s/g, '');
|
|
400
|
+
return crypto.createHash('sha256').update(Buffer.from(der, 'base64')).digest('hex');
|
|
401
|
+
}
|
|
402
|
+
catch {
|
|
403
|
+
return crypto.createHash('sha256').update(pem, 'utf-8').digest('hex');
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
// ── Pending 身份管理 ─────────────────────────────────────
|
|
407
|
+
pendingIdentityDir(aid) {
|
|
408
|
+
const nonce = crypto.randomBytes(4).toString('hex');
|
|
409
|
+
const ts = Math.floor(Date.now() / 1000);
|
|
410
|
+
const dir = join(this._pendingRoot(), `${safeAid(aid)}-${nonce}-${ts}`);
|
|
411
|
+
mkdirSync(join(dir, 'private'), { recursive: true });
|
|
412
|
+
mkdirSync(join(dir, 'public'), { recursive: true });
|
|
413
|
+
return dir;
|
|
414
|
+
}
|
|
415
|
+
listPendingIdentityDirs(aid) {
|
|
416
|
+
const root = this._pendingRoot();
|
|
417
|
+
if (!existsSync(root))
|
|
418
|
+
return [];
|
|
419
|
+
const prefix = `${safeAid(aid)}-`;
|
|
420
|
+
const items = [];
|
|
421
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
422
|
+
if (!entry.isDirectory() || !entry.name.startsWith(prefix))
|
|
423
|
+
continue;
|
|
424
|
+
const path = join(root, entry.name);
|
|
425
|
+
try {
|
|
426
|
+
items.push({ path, mtimeMs: statSync(path).mtimeMs });
|
|
427
|
+
}
|
|
428
|
+
catch { /* ignore */ }
|
|
429
|
+
}
|
|
430
|
+
return items.sort((a, b) => b.mtimeMs - a.mtimeMs).map(item => item.path);
|
|
431
|
+
}
|
|
432
|
+
savePendingKeyPair(pendingDir, aid, keyPair) {
|
|
433
|
+
this._saveKeyPairAtPath(aid, join(pendingDir, 'private', 'key.json'), keyPair);
|
|
434
|
+
}
|
|
435
|
+
loadPendingKeyPair(pendingDir, aid) {
|
|
436
|
+
const keyPath = join(pendingDir, 'private', 'key.json');
|
|
437
|
+
if (!existsSync(keyPath))
|
|
438
|
+
return null;
|
|
439
|
+
let raw;
|
|
440
|
+
try {
|
|
441
|
+
raw = JSON.parse(readFileSync(keyPath, 'utf-8'));
|
|
442
|
+
}
|
|
443
|
+
catch {
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
return this._restoreKeyPair(aid, raw, keyPath);
|
|
447
|
+
}
|
|
448
|
+
savePendingCert(pendingDir, certPem) {
|
|
449
|
+
const certPath = join(pendingDir, 'public', 'cert.pem');
|
|
450
|
+
mkdirSync(dirname(certPath), { recursive: true });
|
|
451
|
+
writeFileSync(certPath, certPem, { encoding: 'utf-8', mode: 0o600 });
|
|
452
|
+
}
|
|
453
|
+
promotePendingIdentity(pendingDir, aid) {
|
|
454
|
+
this._ensurePendingKeyPairProtected(pendingDir, aid);
|
|
455
|
+
const target = join(this._aidsRoot, safeAid(aid));
|
|
456
|
+
if (existsSync(target))
|
|
457
|
+
throw new Error(`promotePendingIdentity: target exists: ${target}`);
|
|
458
|
+
const safe = safeAid(aid);
|
|
459
|
+
const db = this._aidDBs.get(safe);
|
|
460
|
+
if (db) {
|
|
461
|
+
try {
|
|
462
|
+
db.close();
|
|
463
|
+
}
|
|
464
|
+
catch { /* ignore */ }
|
|
465
|
+
this._aidDBs.delete(safe);
|
|
466
|
+
}
|
|
467
|
+
mkdirSync(this._aidsRoot, { recursive: true });
|
|
468
|
+
fsRenameSync(pendingDir, target);
|
|
469
|
+
return target;
|
|
470
|
+
}
|
|
471
|
+
discardPendingIdentity(pendingDir) {
|
|
472
|
+
fsRmSync(pendingDir, { recursive: true, force: true });
|
|
473
|
+
}
|
|
474
|
+
cleanupPendingDirs(maxAgeMs = 600_000) {
|
|
475
|
+
const root = this._pendingRoot();
|
|
476
|
+
if (!existsSync(root))
|
|
477
|
+
return 0;
|
|
478
|
+
let removed = 0;
|
|
479
|
+
const now = Date.now();
|
|
480
|
+
try {
|
|
481
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
482
|
+
if (!entry.isDirectory())
|
|
483
|
+
continue;
|
|
484
|
+
const path = join(root, entry.name);
|
|
485
|
+
try {
|
|
486
|
+
if (now - statSync(path).mtimeMs < maxAgeMs)
|
|
487
|
+
continue;
|
|
488
|
+
fsRmSync(path, { recursive: true, force: true });
|
|
489
|
+
removed++;
|
|
490
|
+
}
|
|
491
|
+
catch (e) {
|
|
492
|
+
this._logger.warn(`cleanupPendingDirs entry failed: ${path} err=${e instanceof Error ? e.message : String(e)}`);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
catch (e) {
|
|
497
|
+
this._logger.warn(`cleanupPendingDirs read root failed: ${root} err=${e instanceof Error ? e.message : String(e)}`);
|
|
498
|
+
}
|
|
499
|
+
return removed;
|
|
500
|
+
}
|
|
501
|
+
_ensurePendingKeyPairProtected(pendingDir, aid) {
|
|
502
|
+
const keyPath = join(pendingDir, 'private', 'key.json');
|
|
503
|
+
if (!existsSync(keyPath))
|
|
504
|
+
throw new Error(`pending identity missing key pair for ${aid}`);
|
|
505
|
+
const raw = JSON.parse(readFileSync(keyPath, 'utf-8'));
|
|
506
|
+
if (typeof raw.private_key_pem === 'string' && raw.private_key_pem)
|
|
507
|
+
throw new Error(`pending identity private key is plaintext for ${aid}`);
|
|
508
|
+
if (!isJsonObject(raw.private_key_protection))
|
|
509
|
+
throw new Error(`pending identity private key is not encrypted for ${aid}`);
|
|
510
|
+
}
|
|
511
|
+
_pendingRoot() {
|
|
512
|
+
return join(this._aidsRoot, '_pending');
|
|
513
|
+
}
|
|
514
|
+
// ── 路径辅助 ─────────────────────────────────────────────
|
|
515
|
+
_keyPairPath(aid) {
|
|
516
|
+
return join(this._aidsRoot, safeAid(aid), 'private', 'key.json');
|
|
517
|
+
}
|
|
518
|
+
_certPath(aid) {
|
|
519
|
+
return join(this._aidsRoot, safeAid(aid), 'public', 'cert.pem');
|
|
520
|
+
}
|
|
521
|
+
_certVersionPath(aid, fp) {
|
|
522
|
+
return join(this._aidsRoot, safeAid(aid), 'public', 'certs', `${fp.replace(/:/g, '_')}.pem`);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
//# sourceMappingURL=local-identity-store.js.map
|