UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

264 lines (263 loc) 11.2 kB
import { n as formatMatrixErrorReason, t as formatMatrixErrorMessage } from "./errors-C4iaVh6O.js"; import { E as writeMatrixRecoveryKeyStateForPath, c as migrateLegacyMatrixRecoveryKeyFilePathToStore, m as readLegacyMatrixRecoveryKeyFile, y as readMatrixRecoveryKeyStateForPath } from "./crypto-state-store-Dh3bqPTW.js"; import { n as LogService } from "./logger-LOBoLzE6.js"; import path from "node:path"; import { decodeRecoveryKey } from "matrix-js-sdk/lib/crypto-api/recovery-key.js"; //#region extensions/matrix/src/matrix/sdk/recovery-key-store.ts function isRepairableSecretStorageAccessError(err) { const message = formatMatrixErrorReason(err); if (!message) return false; if (message.includes("getsecretstoragekey callback returned falsey")) return true; if (message.includes("decrypting secret") && message.includes("bad mac")) return true; return false; } var MatrixRecoveryKeyStore = class { constructor(recoveryKeyPath) { this.secretStorageKeyCache = /* @__PURE__ */ new Map(); this.stagedRecoveryKey = null; this.stagedRecoveryKeyUsed = false; this.stagedCacheKeyIds = /* @__PURE__ */ new Set(); this.recoveryKeyPath = recoveryKeyPath; this.storageRootDir = recoveryKeyPath ? path.dirname(recoveryKeyPath) : void 0; if (this.recoveryKeyPath) try { migrateLegacyMatrixRecoveryKeyFilePathToStore(this.recoveryKeyPath); } catch (err) { this.legacyRecoveryKeyPathOnMigrationFailure = this.recoveryKeyPath; LogService.warn("MatrixClientLite", "Failed to migrate Matrix recovery key state:", err); } } buildCryptoCallbacks() { return { getSecretStorageKey: async ({ keys }) => { const requestedKeyIds = Object.keys(keys ?? {}); if (requestedKeyIds.length === 0) return null; const staged = this.resolveStagedSecretStorageKey(requestedKeyIds); if (staged) return staged; for (const keyId of requestedKeyIds) { const cached = this.secretStorageKeyCache.get(keyId); if (cached) return [keyId, new Uint8Array(cached.key)]; } const stored = this.loadStoredRecoveryKey(); if (!stored?.privateKeyBase64) return null; const privateKey = new Uint8Array(Buffer.from(stored.privateKeyBase64, "base64")); if (privateKey.length === 0) return null; if (stored.keyId && requestedKeyIds.includes(stored.keyId)) { this.rememberSecretStorageKey(stored.keyId, privateKey, stored.keyInfo); return [stored.keyId, privateKey]; } const firstRequestedKeyId = requestedKeyIds[0]; if (!firstRequestedKeyId) return null; this.rememberSecretStorageKey(firstRequestedKeyId, privateKey, stored.keyInfo); return [firstRequestedKeyId, privateKey]; }, cacheSecretStorageKey: (keyId, keyInfo, key) => { const privateKey = new Uint8Array(key); const normalizedKeyInfo = { passphrase: keyInfo?.passphrase, name: typeof keyInfo?.name === "string" ? keyInfo.name : void 0 }; this.rememberSecretStorageKey(keyId, privateKey, normalizedKeyInfo); const stored = this.loadStoredRecoveryKey(); this.saveRecoveryKeyToDisk({ keyId, keyInfo: normalizedKeyInfo, privateKey, encodedPrivateKey: stored?.encodedPrivateKey }); } }; } getRecoveryKeySummary() { const stored = this.loadStoredRecoveryKey(); if (!stored) return null; return { encodedPrivateKey: stored.encodedPrivateKey, keyId: stored.keyId, createdAt: stored.createdAt }; } resolveEncodedRecoveryKeyInput(params) { const encodedPrivateKey = params.encodedPrivateKey.trim(); if (!encodedPrivateKey) throw new Error("Matrix recovery key is required"); let privateKey; try { privateKey = decodeRecoveryKey(encodedPrivateKey); } catch (err) { throw new Error(`Invalid Matrix recovery key: ${formatMatrixErrorMessage(err)}`, { cause: err }); } const keyId = typeof params.keyId === "string" && params.keyId.trim() ? params.keyId.trim() : null; return { encodedPrivateKey, privateKey, keyId, keyInfo: params.keyInfo ?? this.loadStoredRecoveryKey()?.keyInfo }; } storeEncodedRecoveryKey(params) { const prepared = this.resolveEncodedRecoveryKeyInput(params); this.saveRecoveryKeyToDisk({ keyId: prepared.keyId, keyInfo: prepared.keyInfo, privateKey: prepared.privateKey, encodedPrivateKey: prepared.encodedPrivateKey }); if (prepared.keyId) this.rememberSecretStorageKey(prepared.keyId, prepared.privateKey, prepared.keyInfo); return this.getRecoveryKeySummary() ?? {}; } stageEncodedRecoveryKey(params) { const prepared = this.resolveEncodedRecoveryKeyInput(params); this.discardStagedRecoveryKey(); this.stagedRecoveryKey = { version: 1, createdAt: (/* @__PURE__ */ new Date()).toISOString(), keyId: prepared.keyId, encodedPrivateKey: prepared.encodedPrivateKey, privateKeyBase64: Buffer.from(prepared.privateKey).toString("base64"), keyInfo: prepared.keyInfo }; } hasStagedRecoveryKeyBeenUsed() { return this.stagedRecoveryKeyUsed; } commitStagedRecoveryKey(params) { if (!this.stagedRecoveryKey) return this.getRecoveryKeySummary(); const staged = this.stagedRecoveryKey; const privateKey = new Uint8Array(Buffer.from(staged.privateKeyBase64, "base64")); const keyId = typeof params?.keyId === "string" && params.keyId.trim() ? params.keyId.trim() : staged.keyId; this.saveRecoveryKeyToDisk({ keyId, keyInfo: params?.keyInfo ?? staged.keyInfo, privateKey, encodedPrivateKey: staged.encodedPrivateKey }); this.clearStagedRecoveryKeyTracking(); return this.getRecoveryKeySummary(); } discardStagedRecoveryKey() { for (const keyId of this.stagedCacheKeyIds) this.secretStorageKeyCache.delete(keyId); this.clearStagedRecoveryKeyTracking(); } async bootstrapSecretStorageWithRecoveryKey(crypto, options = {}) { let status = null; const getSecretStorageStatus = crypto.getSecretStorageStatus; if (typeof getSecretStorageStatus === "function") try { status = await getSecretStorageStatus.call(crypto); } catch (err) { LogService.warn("MatrixClientLite", "Failed to read secret storage status:", err); } const hasDefaultSecretStorageKey = Boolean(status?.defaultKeyId); const hasKnownInvalidSecrets = Object.values(status?.secretStorageKeyValidityMap ?? {}).some((valid) => !valid); let generatedRecoveryKey = false; const storedRecovery = this.loadStoredRecoveryKey(); const stagedRecovery = this.stagedRecoveryKey; const sourceRecovery = options.forceNewRecoveryKey === true ? null : stagedRecovery ?? storedRecovery; let recoveryKey = sourceRecovery ? { keyInfo: sourceRecovery.keyInfo, privateKey: new Uint8Array(Buffer.from(sourceRecovery.privateKeyBase64, "base64")), encodedPrivateKey: sourceRecovery.encodedPrivateKey } : null; if (recoveryKey && status?.defaultKeyId) { const defaultKeyId = status.defaultKeyId; if (!stagedRecovery) { this.rememberSecretStorageKey(defaultKeyId, recoveryKey.privateKey, recoveryKey.keyInfo); if (storedRecovery && storedRecovery.keyId !== defaultKeyId) this.saveRecoveryKeyToDisk({ keyId: defaultKeyId, keyInfo: recoveryKey.keyInfo, privateKey: recoveryKey.privateKey, encodedPrivateKey: recoveryKey.encodedPrivateKey }); } } const ensureRecoveryKey = async () => { if (recoveryKey) { if (stagedRecovery) this.stagedRecoveryKeyUsed = true; return recoveryKey; } if (typeof crypto.createRecoveryKeyFromPassphrase !== "function") throw new Error("Matrix crypto backend does not support recovery key generation (createRecoveryKeyFromPassphrase missing)"); recoveryKey = await crypto.createRecoveryKeyFromPassphrase(); this.saveRecoveryKeyToDisk(recoveryKey); generatedRecoveryKey = true; return recoveryKey; }; const shouldRecreateSecretStorage = options.forceNewSecretStorage === true || !hasDefaultSecretStorageKey || !recoveryKey && status?.ready === false || hasKnownInvalidSecrets; if (hasKnownInvalidSecrets) recoveryKey = null; const secretStorageOptions = { setupNewKeyBackup: options.setupNewKeyBackup === true }; if (shouldRecreateSecretStorage) { secretStorageOptions.setupNewSecretStorage = true; secretStorageOptions.createSecretStorageKey = ensureRecoveryKey; } try { await crypto.bootstrapSecretStorage(secretStorageOptions); } catch (err) { if (!(options.allowSecretStorageRecreateWithoutRecoveryKey === true && hasDefaultSecretStorageKey && isRepairableSecretStorageAccessError(err))) throw err; recoveryKey = null; LogService.warn("MatrixClientLite", "Secret storage exists on the server but local recovery material cannot unlock it; recreating secret storage during explicit bootstrap."); await crypto.bootstrapSecretStorage({ setupNewSecretStorage: true, setupNewKeyBackup: options.setupNewKeyBackup === true, createSecretStorageKey: ensureRecoveryKey }); } if (generatedRecoveryKey && this.storageRootDir) LogService.warn("MatrixClientLite", "Generated Matrix recovery key and saved it to Matrix SQLite state. Keep the displayed recovery key secure."); } clearStagedRecoveryKeyTracking() { this.stagedRecoveryKey = null; this.stagedRecoveryKeyUsed = false; this.stagedCacheKeyIds.clear(); } resolveStagedSecretStorageKey(requestedKeyIds) { const staged = this.stagedRecoveryKey; if (!staged?.privateKeyBase64) return null; const privateKey = new Uint8Array(Buffer.from(staged.privateKeyBase64, "base64")); if (privateKey.length === 0) return null; const keyId = staged.keyId && requestedKeyIds.includes(staged.keyId) ? staged.keyId : requestedKeyIds[0]; if (!keyId) return null; this.rememberStagedSecretStorageKey(keyId, privateKey, staged.keyInfo); this.stagedCacheKeyIds.add(keyId); return [keyId, privateKey]; } rememberStagedSecretStorageKey(keyId, key, keyInfo) { this.stagedRecoveryKeyUsed = true; this.rememberSecretStorageKey(keyId, key, keyInfo); } rememberSecretStorageKey(keyId, key, keyInfo) { if (!keyId.trim()) return; this.secretStorageKeyCache.set(keyId, { key: new Uint8Array(key), keyInfo }); } loadStoredRecoveryKey() { if (!this.recoveryKeyPath) return null; try { const stored = readMatrixRecoveryKeyStateForPath(this.recoveryKeyPath); if (stored) return stored; } catch {} if (this.legacyRecoveryKeyPathOnMigrationFailure) return readLegacyMatrixRecoveryKeyFile(this.legacyRecoveryKeyPathOnMigrationFailure); return null; } saveRecoveryKeyToDisk(params) { if (!this.recoveryKeyPath) return; try { const payload = { version: 1, createdAt: (/* @__PURE__ */ new Date()).toISOString(), keyId: typeof params.keyId === "string" ? params.keyId : null, encodedPrivateKey: params.encodedPrivateKey, privateKeyBase64: Buffer.from(params.privateKey).toString("base64"), keyInfo: params.keyInfo ? { passphrase: params.keyInfo.passphrase, name: params.keyInfo.name } : void 0 }; writeMatrixRecoveryKeyStateForPath({ recoveryKeyPath: this.recoveryKeyPath, payload }); } catch (err) { LogService.warn("MatrixClientLite", "Failed to persist recovery key:", err); } } }; //#endregion export { isRepairableSecretStorageAccessError as n, MatrixRecoveryKeyStore as t };