forked from CringeStudios/element-desktop
Migrate from keytar to safeStorage
This commit is contained in:
parent
fedaba9583
commit
08d844f89f
10
src/@types/global.d.ts
vendored
10
src/@types/global.d.ts
vendored
@ -19,6 +19,7 @@ import Store from "electron-store";
|
||||
import AutoLaunch from "auto-launch";
|
||||
|
||||
import { AppLocalization } from "../language-helper";
|
||||
import { StoreData } from "../electron-main";
|
||||
|
||||
// global type extensions need to use var for whatever reason
|
||||
/* eslint-disable no-var */
|
||||
@ -33,13 +34,6 @@ declare global {
|
||||
icon_path: string;
|
||||
brand: string;
|
||||
};
|
||||
var store: Store<{
|
||||
warnBeforeExit?: boolean;
|
||||
minimizeToTray?: boolean;
|
||||
spellCheckerEnabled?: boolean;
|
||||
autoHideMenuBar?: boolean;
|
||||
locale?: string | string[];
|
||||
disableHardwareAcceleration?: boolean;
|
||||
}>;
|
||||
var store: Store<StoreData>;
|
||||
}
|
||||
/* eslint-enable no-var */
|
||||
|
@ -30,10 +30,10 @@ import { URL } from "url";
|
||||
import minimist from "minimist";
|
||||
|
||||
import "./ipc";
|
||||
import "./keytar";
|
||||
import "./seshat";
|
||||
import "./settings";
|
||||
import * as tray from "./tray";
|
||||
import { migrate as migrateSafeStorage } from "./safe-storage";
|
||||
import { buildMenuTemplate } from "./vectormenu";
|
||||
import webContentsHandler from "./webcontents-handler";
|
||||
import * as updater from "./updater";
|
||||
@ -252,7 +252,53 @@ async function moveAutoLauncher(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
global.store = new Store({ name: "electron-config" });
|
||||
export interface StoreData {
|
||||
warnBeforeExit: boolean;
|
||||
minimizeToTray: boolean;
|
||||
spellCheckerEnabled: boolean;
|
||||
autoHideMenuBar: boolean;
|
||||
locale?: string | string[];
|
||||
disableHardwareAcceleration: boolean;
|
||||
migratedToSafeStorage: boolean;
|
||||
safeStorage: Record<string, string>;
|
||||
}
|
||||
|
||||
global.store = new Store({
|
||||
name: "electron-config",
|
||||
schema: {
|
||||
warnBeforeExit: {
|
||||
type: "boolean",
|
||||
default: true,
|
||||
},
|
||||
minimizeToTray: {
|
||||
type: "boolean",
|
||||
default: true,
|
||||
},
|
||||
spellCheckerEnabled: {
|
||||
type: "boolean",
|
||||
default: true,
|
||||
},
|
||||
autoHideMenuBar: {
|
||||
type: "boolean",
|
||||
default: true,
|
||||
},
|
||||
locale: {
|
||||
anyOf: [{ type: "string" }, { type: "array", items: { type: "string" } }],
|
||||
},
|
||||
disableHardwareAcceleration: {
|
||||
type: "boolean",
|
||||
default: false,
|
||||
},
|
||||
migratedToSafeStorage: {
|
||||
type: "boolean",
|
||||
default: false,
|
||||
},
|
||||
safeStorage: {
|
||||
type: "object",
|
||||
additionalProperties: { type: "string" },
|
||||
},
|
||||
},
|
||||
}) as Store<StoreData>;
|
||||
|
||||
global.appQuitting = false;
|
||||
|
||||
@ -345,12 +391,14 @@ app.enableSandbox();
|
||||
app.commandLine.appendSwitch("disable-features", "HardwareMediaKeyHandling,MediaSessionService");
|
||||
|
||||
// Disable hardware acceleration if the setting has been set.
|
||||
if (global.store.get("disableHardwareAcceleration", false) === true) {
|
||||
if (global.store.get("disableHardwareAcceleration") === true) {
|
||||
console.log("Disabling hardware acceleration.");
|
||||
app.disableHardwareAcceleration();
|
||||
}
|
||||
|
||||
app.on("ready", async () => {
|
||||
await migrateSafeStorage();
|
||||
|
||||
let asarPath: string;
|
||||
|
||||
try {
|
||||
@ -456,7 +504,7 @@ app.on("ready", async () => {
|
||||
|
||||
icon: global.trayConfig.icon_path,
|
||||
show: false,
|
||||
autoHideMenuBar: global.store.get("autoHideMenuBar", true),
|
||||
autoHideMenuBar: global.store.get("autoHideMenuBar"),
|
||||
|
||||
x: mainWindowState.x,
|
||||
y: mainWindowState.y,
|
||||
@ -477,7 +525,7 @@ app.on("ready", async () => {
|
||||
global.mainWindow.webContents.session.setSpellCheckerEnabled(global.store.get("spellCheckerEnabled", true));
|
||||
|
||||
// Create trayIcon icon
|
||||
if (global.store.get("minimizeToTray", true)) tray.create(global.trayConfig);
|
||||
if (global.store.get("minimizeToTray")) tray.create(global.trayConfig);
|
||||
|
||||
global.mainWindow.once("ready-to-show", () => {
|
||||
if (!global.mainWindow) return;
|
||||
|
18
src/ipc.ts
18
src/ipc.ts
@ -21,7 +21,7 @@ import IpcMainEvent = Electron.IpcMainEvent;
|
||||
import { recordSSOSession } from "./protocol";
|
||||
import { randomArray } from "./utils";
|
||||
import { Settings } from "./settings";
|
||||
import { keytar } from "./keytar";
|
||||
import { deletePassword, getPassword, setPassword } from "./safe-storage";
|
||||
import { getDisplayMediaCallback, setDisplayMediaCallback } from "./displayMediaCallback";
|
||||
|
||||
ipcMain.on("setBadgeCount", function (_ev: IpcMainEvent, count: number): void {
|
||||
@ -125,7 +125,7 @@ ipcMain.on("ipcCall", async function (_ev: IpcMainEvent, payload) {
|
||||
break;
|
||||
|
||||
case "getSpellCheckEnabled":
|
||||
ret = global.store.get("spellCheckerEnabled", true);
|
||||
ret = global.store.get("spellCheckerEnabled");
|
||||
break;
|
||||
|
||||
case "setSpellCheckLanguages":
|
||||
@ -149,12 +149,7 @@ ipcMain.on("ipcCall", async function (_ev: IpcMainEvent, payload) {
|
||||
|
||||
case "getPickleKey":
|
||||
try {
|
||||
ret = await keytar?.getPassword("element.io", `${args[0]}|${args[1]}`);
|
||||
// migrate from riot.im (remove once we think there will no longer be
|
||||
// logins from the time of riot.im)
|
||||
if (ret === null) {
|
||||
ret = await keytar?.getPassword("riot.im", `${args[0]}|${args[1]}`);
|
||||
}
|
||||
ret = await getPassword(`${args[0]}|${args[1]}`);
|
||||
} catch (e) {
|
||||
// if an error is thrown (e.g. keytar can't connect to the keychain),
|
||||
// then return null, which means the default pickle key will be used
|
||||
@ -165,7 +160,7 @@ ipcMain.on("ipcCall", async function (_ev: IpcMainEvent, payload) {
|
||||
case "createPickleKey":
|
||||
try {
|
||||
const pickleKey = await randomArray(32);
|
||||
await keytar?.setPassword("element.io", `${args[0]}|${args[1]}`, pickleKey);
|
||||
await setPassword(`${args[0]}|${args[1]}`, pickleKey);
|
||||
ret = pickleKey;
|
||||
} catch (e) {
|
||||
ret = null;
|
||||
@ -174,10 +169,7 @@ ipcMain.on("ipcCall", async function (_ev: IpcMainEvent, payload) {
|
||||
|
||||
case "destroyPickleKey":
|
||||
try {
|
||||
await keytar?.deletePassword("element.io", `${args[0]}|${args[1]}`);
|
||||
// migrate from riot.im (remove once we think there will no longer be
|
||||
// logins from the time of riot.im)
|
||||
await keytar?.deletePassword("riot.im", `${args[0]}|${args[1]}`);
|
||||
await deletePassword(`${args[0]}|${args[1]}`);
|
||||
} catch (e) {}
|
||||
break;
|
||||
case "getDesktopCapturerSources":
|
||||
|
@ -1,31 +0,0 @@
|
||||
/*
|
||||
Copyright 2022 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 * as Keytar from "keytar"; // Hak dependency type
|
||||
|
||||
let keytar: typeof Keytar | undefined;
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
keytar = require("keytar");
|
||||
} catch (e) {
|
||||
if ((<NodeJS.ErrnoException>e).code === "MODULE_NOT_FOUND") {
|
||||
console.log("Keytar isn't installed; secure key storage is disabled.");
|
||||
} else {
|
||||
console.warn("Keytar unexpected error:", e);
|
||||
}
|
||||
}
|
||||
|
||||
export { keytar };
|
@ -16,8 +16,6 @@ limitations under the License.
|
||||
|
||||
import counterpart from "counterpart";
|
||||
|
||||
import type Store from "electron-store";
|
||||
|
||||
const FALLBACK_LOCALE = "en";
|
||||
|
||||
export function _td(text: string): string {
|
||||
@ -63,7 +61,7 @@ export function _t(text: string, variables: IVariables = {}): string {
|
||||
|
||||
type Component = () => void;
|
||||
|
||||
type TypedStore = Store<{ locale?: string | string[] }>;
|
||||
type TypedStore = (typeof global)["store"];
|
||||
|
||||
export class AppLocalization {
|
||||
private static readonly STORE_KEY = "locale";
|
||||
|
106
src/safe-storage.ts
Normal file
106
src/safe-storage.ts
Normal file
@ -0,0 +1,106 @@
|
||||
/*
|
||||
Copyright 2022 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 { safeStorage } from "electron";
|
||||
|
||||
import type * as Keytar from "keytar";
|
||||
|
||||
const KEYTAR_SERVICE = "element.io";
|
||||
const LEGACY_KEYTAR_SERVICE = "riot.im";
|
||||
|
||||
let keytar: typeof Keytar | undefined;
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
keytar = require("keytar");
|
||||
} catch (e) {
|
||||
if ((<NodeJS.ErrnoException>e).code === "MODULE_NOT_FOUND") {
|
||||
console.log("Keytar isn't installed; secure key storage is disabled.");
|
||||
} else {
|
||||
console.warn("Keytar unexpected error:", e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function migrate(): Promise<void> {
|
||||
if (global.store.get("migratedToSafeStorage")) return; // already done
|
||||
|
||||
if (keytar) {
|
||||
const credentials = [
|
||||
...(await keytar.findCredentials(LEGACY_KEYTAR_SERVICE)),
|
||||
...(await keytar.findCredentials(KEYTAR_SERVICE)),
|
||||
];
|
||||
credentials.forEach((cred) => {
|
||||
deletePassword(cred.account);
|
||||
setPassword(cred.account, cred.password);
|
||||
});
|
||||
}
|
||||
|
||||
global.store.set("migratedToSafeStorage", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stored password for the key.
|
||||
*
|
||||
* @param key The string key name.
|
||||
*
|
||||
* @returns A promise for the password string.
|
||||
*/
|
||||
export async function getPassword(key: string): Promise<string | null> {
|
||||
if (safeStorage.isEncryptionAvailable()) {
|
||||
const encryptedValue = global.store.get(`safeStorage.${key}`);
|
||||
if (typeof encryptedValue === "string") {
|
||||
return safeStorage.decryptString(Buffer.from(encryptedValue));
|
||||
}
|
||||
}
|
||||
if (keytar) {
|
||||
return (
|
||||
(await keytar.getPassword(KEYTAR_SERVICE, key)) ?? (await keytar.getPassword(LEGACY_KEYTAR_SERVICE, key))
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the password for the key to the keychain.
|
||||
*
|
||||
* @param key The string key name.
|
||||
* @param password The string password.
|
||||
*
|
||||
* @returns A promise for the set password completion.
|
||||
*/
|
||||
export async function setPassword(key: string, password: string): Promise<void> {
|
||||
if (safeStorage.isEncryptionAvailable()) {
|
||||
const encryptedValue = safeStorage.encryptString(password);
|
||||
global.store.set(`safeStorage.${key}`, encryptedValue.toString());
|
||||
}
|
||||
await keytar?.setPassword(KEYTAR_SERVICE, key, password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the stored password for the key.
|
||||
*
|
||||
* @param key The string key name.
|
||||
*
|
||||
* @returns A promise for the deletion status. True on success.
|
||||
*/
|
||||
export async function deletePassword(key: string): Promise<boolean> {
|
||||
if (safeStorage.isEncryptionAvailable()) {
|
||||
global.store.delete(`safeStorage.${key}`);
|
||||
await keytar?.deletePassword(LEGACY_KEYTAR_SERVICE, key);
|
||||
await keytar?.deletePassword(KEYTAR_SERVICE, key);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
@ -25,7 +25,7 @@ import type {
|
||||
} from "matrix-seshat"; // Hak dependency type
|
||||
import IpcMainEvent = Electron.IpcMainEvent;
|
||||
import { randomArray } from "./utils";
|
||||
import { keytar } from "./keytar";
|
||||
import { getPassword, setPassword } from "./safe-storage";
|
||||
|
||||
let seshatSupported = false;
|
||||
let Seshat: typeof SeshatType;
|
||||
@ -51,19 +51,17 @@ let eventIndex: SeshatType | null = null;
|
||||
|
||||
const seshatDefaultPassphrase = "DEFAULT_PASSPHRASE";
|
||||
async function getOrCreatePassphrase(key: string): Promise<string> {
|
||||
if (keytar) {
|
||||
try {
|
||||
const storedPassphrase = await keytar.getPassword("element.io", key);
|
||||
if (storedPassphrase !== null) {
|
||||
return storedPassphrase;
|
||||
} else {
|
||||
const newPassphrase = await randomArray(32);
|
||||
await keytar.setPassword("element.io", key, newPassphrase);
|
||||
return newPassphrase;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Error getting the event index passphrase out of the secret store", e);
|
||||
try {
|
||||
const storedPassphrase = await getPassword(key);
|
||||
if (storedPassphrase !== null) {
|
||||
return storedPassphrase;
|
||||
} else {
|
||||
const newPassphrase = await randomArray(32);
|
||||
await setPassword(key, newPassphrase);
|
||||
return newPassphrase;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Error getting the event index passphrase out of the secret store", e);
|
||||
}
|
||||
return seshatDefaultPassphrase;
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ export const Settings: Record<string, Setting> = {
|
||||
},
|
||||
"Electron.warnBeforeExit": {
|
||||
async read(): Promise<any> {
|
||||
return global.store.get("warnBeforeExit", true);
|
||||
return global.store.get("warnBeforeExit");
|
||||
},
|
||||
async write(value: any): Promise<void> {
|
||||
global.store.set("warnBeforeExit", value);
|
||||
@ -70,7 +70,7 @@ export const Settings: Record<string, Setting> = {
|
||||
},
|
||||
"Electron.enableHardwareAcceleration": {
|
||||
async read(): Promise<any> {
|
||||
return !global.store.get("disableHardwareAcceleration", false);
|
||||
return !global.store.get("disableHardwareAcceleration");
|
||||
},
|
||||
async write(value: any): Promise<void> {
|
||||
global.store.set("disableHardwareAcceleration", !value);
|
||||
|
Loading…
x
Reference in New Issue
Block a user