From 503c42f4f53f1a00bfe6dbe63d58520c17e43bfa Mon Sep 17 00:00:00 2001 From: Nils Hanff Date: Thu, 31 Aug 2023 11:42:27 +0200 Subject: [PATCH] Enable generic secrets on local machine via ipc Signed-off-by: Nils Hanff --- src/ipc.ts | 21 ++++++++++++++++ src/secrets.ts | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 src/secrets.ts diff --git a/src/ipc.ts b/src/ipc.ts index b84fb9514..c539ac7ab 100644 --- a/src/ipc.ts +++ b/src/ipc.ts @@ -23,6 +23,7 @@ import { randomArray } from "./utils"; import { Settings } from "./settings"; import { keytar } from "./keytar"; import { getDisplayMediaCallback, setDisplayMediaCallback } from "./displayMediaCallback"; +import { getSecretStore } from "./secrets"; ipcMain.on("setBadgeCount", function (_ev: IpcMainEvent, count: number): void { if (process.platform !== "win32") { @@ -147,6 +148,26 @@ ipcMain.on("ipcCall", async function (_ev: IpcMainEvent, payload) { recordSSOSession(args[0]); break; + case "getSecret": + try { + ret = await getSecretStore().getSecret(args[0]); + } catch (e) { + ret = null; + } + break; + case "saveSecret": + try { + ret = await getSecretStore().saveSecret(args[0], args[1]); + } catch (e) { + ret = null; + } + break; + case "destroySecret": + try { + ret = await getSecretStore().destroySecret(args[0]); + } catch (e) {} + break; + case "getPickleKey": try { ret = await keytar?.getPassword("element.io", `${args[0]}|${args[1]}`); diff --git a/src/secrets.ts b/src/secrets.ts new file mode 100644 index 000000000..3056b50f3 --- /dev/null +++ b/src/secrets.ts @@ -0,0 +1,66 @@ +/* +Copyright 2023 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { type SafeStorage, safeStorage } from "electron"; + +/** + * Save secrets on a local machine until logout. + */ +interface SecretStore { + saveSecret(name: string, value: string): Promise; + getSecret(name: string): Promise; + destroySecret(name: string): Promise; +} + +class SafeStorageSecretStore implements SecretStore { + public constructor(public readonly safeStorage: SafeStorage) { } + + public async saveSecret(name: string, value: string): Promise { + global.store.set(this.storeKey(name), this.safeStorage.encryptString(value).toString("base64")); + return await this.getSecret(this.storeKey(name)); + } + public async getSecret(name: string): Promise { + return this.safeStorage.decryptString(Buffer.from(global.store.get(this.storeKey(name)) as string, "base64")); + } + public async destroySecret(name: string): Promise { + global.store.delete(this.storeKey(name) as any); + } + + private storeKey(name: string): string { + return `safeStorage.${name}`; + } +} + +class NullSecretStore implements SecretStore { + public async saveSecret(): Promise { return null; } + public async getSecret(): Promise { return null; } + public async destroySecret(): Promise { } +} + +function createSecretStore(): SecretStore { + if (safeStorage.isEncryptionAvailable() && safeStorage.getSelectedStorageBackend() !== "basic_text") { + return new SafeStorageSecretStore(safeStorage); + } + return new NullSecretStore(); +} + +let secretStore: SecretStore | null = null; +export function getSecretStore(): SecretStore { + if (!secretStore) { + secretStore = createSecretStore(); + } + return secretStore; +}