mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2025-12-20 10:19:08 +00:00
Test safeStorage in Flatpak environments
This commit is contained in:
@@ -155,6 +155,18 @@
|
|||||||
"messageformat": "Unable to access the database encryption key because the OS encryption keyring backend has changed from {previousBackend} to {currentBackend}. This can occur if the desktop environment changes, for example between GNOME and KDE.\n\nPlease switch to the previous desktop environment or try to run signal with the command line flag --password-store=\"{previousBackendFlag}\"",
|
"messageformat": "Unable to access the database encryption key because the OS encryption keyring backend has changed from {previousBackend} to {currentBackend}. This can occur if the desktop environment changes, for example between GNOME and KDE.\n\nPlease switch to the previous desktop environment or try to run signal with the command line flag --password-store=\"{previousBackendFlag}\"",
|
||||||
"description": "On Linux, text in a popup shown if the app cannot start because the system's keyring encryption backend has changed. We suggest a command line flag they can use to recover the app. Example values: previousBackend=gnome_libsecret, currentBackend=kwallet5, previousBackendFlag: gnome-libsecret"
|
"description": "On Linux, text in a popup shown if the app cannot start because the system's keyring encryption backend has changed. We suggest a command line flag they can use to recover the app. Example values: previousBackend=gnome_libsecret, currentBackend=kwallet5, previousBackendFlag: gnome-libsecret"
|
||||||
},
|
},
|
||||||
|
"icu:systemEncryptionError": {
|
||||||
|
"messageformat": "System encryption error",
|
||||||
|
"description": "On Linux, title in a popup shown when the app detects that the system keyring encryption does not work correctly."
|
||||||
|
},
|
||||||
|
"icu:systemEncryptionError__linuxSafeStorageDecryptionError": {
|
||||||
|
"messageformat": "Unable to decrypt the database encryption key using the OS encryption keyring. This can occur in certain containerized environments such as Flatpak.\n\nWe recommend quitting and checking your OS encryption backend such as gnome-libsecret or kwallet, then trying again.\n\nYou may choose to continue anyway, however the database key will be stored in plaintext on the filesystem and other apps may be able to access it.",
|
||||||
|
"description": "On Linux, text in a popup shown when the app detects that the system keyring encryption does not work correctly."
|
||||||
|
},
|
||||||
|
"icu:systemEncryptionError__continueWithPlaintextKey": {
|
||||||
|
"messageformat": "Continue with plaintext key",
|
||||||
|
"description": "On Linux, button in a popup shown when the app detects that the system keyring encryption does not work correctly."
|
||||||
|
},
|
||||||
"icu:mainMenuFile": {
|
"icu:mainMenuFile": {
|
||||||
"messageformat": "&File",
|
"messageformat": "&File",
|
||||||
"description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt-<letter> combination."
|
"description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt-<letter> combination."
|
||||||
|
|||||||
63
app/main.ts
63
app/main.ts
@@ -123,6 +123,7 @@ import { parseSignalRoute } from '../ts/util/signalRoutes.js';
|
|||||||
import * as dns from '../ts/util/dns.js';
|
import * as dns from '../ts/util/dns.js';
|
||||||
import { ZoomFactorService } from '../ts/services/ZoomFactorService.js';
|
import { ZoomFactorService } from '../ts/services/ZoomFactorService.js';
|
||||||
import { SafeStorageBackendChangeError } from '../ts/types/SafeStorageBackendChangeError.js';
|
import { SafeStorageBackendChangeError } from '../ts/types/SafeStorageBackendChangeError.js';
|
||||||
|
import { SafeStorageDecryptionError } from '../ts/types/SafeStorageDecryptionError.js';
|
||||||
import { LINUX_PASSWORD_STORE_FLAGS } from '../ts/util/linuxPasswordStoreFlags.js';
|
import { LINUX_PASSWORD_STORE_FLAGS } from '../ts/util/linuxPasswordStoreFlags.js';
|
||||||
import { getOwn } from '../ts/util/getOwn.js';
|
import { getOwn } from '../ts/util/getOwn.js';
|
||||||
import { safeParseLoose, safeParseUnknown } from '../ts/util/schemas.js';
|
import { safeParseLoose, safeParseUnknown } from '../ts/util/schemas.js';
|
||||||
@@ -1650,9 +1651,20 @@ function getSQLKey(): string {
|
|||||||
const encrypted = Buffer.from(modernKeyValue, 'hex');
|
const encrypted = Buffer.from(modernKeyValue, 'hex');
|
||||||
key = safeStorage.decryptString(encrypted);
|
key = safeStorage.decryptString(encrypted);
|
||||||
|
|
||||||
if (legacyKeyValue != null) {
|
if (typeof legacyKeyValue === 'string') {
|
||||||
|
if (key === legacyKeyValue) {
|
||||||
|
// Confirmed roundtrip encryption, we can remove the legacy key
|
||||||
log.info('getSQLKey: removing legacy key');
|
log.info('getSQLKey: removing legacy key');
|
||||||
userConfig.set('key', undefined);
|
userConfig.set('key', undefined);
|
||||||
|
} else {
|
||||||
|
log.warn('getSQLKey: decrypted modern key mismatch with legacy key');
|
||||||
|
const nextStep = handleSafeStorageDecryptionError();
|
||||||
|
if (nextStep === 'quit') {
|
||||||
|
throw new SafeStorageDecryptionError();
|
||||||
|
}
|
||||||
|
|
||||||
|
key = legacyKeyValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLinux && previousBackend == null) {
|
if (isLinux && previousBackend == null) {
|
||||||
@@ -1681,7 +1693,15 @@ function getSQLKey(): string {
|
|||||||
log.info('getSQLKey: updating encrypted key in the config');
|
log.info('getSQLKey: updating encrypted key in the config');
|
||||||
const encrypted = safeStorage.encryptString(key).toString('hex');
|
const encrypted = safeStorage.encryptString(key).toString('hex');
|
||||||
userConfig.set('encryptedKey', encrypted);
|
userConfig.set('encryptedKey', encrypted);
|
||||||
|
|
||||||
|
if (OS.isFlatpak()) {
|
||||||
|
log.info(
|
||||||
|
'getSQLKey: updating plaintext key in the config, will confirm decryption on next start'
|
||||||
|
);
|
||||||
|
userConfig.set('key', key);
|
||||||
|
} else {
|
||||||
userConfig.set('key', undefined);
|
userConfig.set('key', undefined);
|
||||||
|
}
|
||||||
|
|
||||||
if (isLinux && safeStorageBackend) {
|
if (isLinux && safeStorageBackend) {
|
||||||
log.info(`getSQLKey: saving safeStorageBackend: ${safeStorageBackend}`);
|
log.info(`getSQLKey: saving safeStorageBackend: ${safeStorageBackend}`);
|
||||||
@@ -1695,6 +1715,41 @@ function getSQLKey(): string {
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In Flatpak, safeStorage encryption may appear to work on the first run but on
|
||||||
|
// subsequent starts the decrypted value may be incorrect.
|
||||||
|
function handleSafeStorageDecryptionError(): 'continue' | 'quit' {
|
||||||
|
const previousError = userConfig.get('safeStorageDecryptionError');
|
||||||
|
if (typeof previousError === 'string') {
|
||||||
|
return 'continue';
|
||||||
|
}
|
||||||
|
|
||||||
|
const { i18n } = getResolvedMessagesLocale();
|
||||||
|
const message = i18n('icu:systemEncryptionError');
|
||||||
|
const detail = i18n(
|
||||||
|
'icu:systemEncryptionError__linuxSafeStorageDecryptionError'
|
||||||
|
);
|
||||||
|
const buttons = [
|
||||||
|
i18n('icu:copyErrorAndQuit'),
|
||||||
|
i18n('icu:systemEncryptionError__continueWithPlaintextKey'),
|
||||||
|
];
|
||||||
|
const copyErrorAndQuitIndex = 0;
|
||||||
|
const resultIndex = dialog.showMessageBoxSync({
|
||||||
|
buttons,
|
||||||
|
defaultId: copyErrorAndQuitIndex,
|
||||||
|
cancelId: copyErrorAndQuitIndex,
|
||||||
|
message,
|
||||||
|
detail,
|
||||||
|
icon: getAppErrorIcon(),
|
||||||
|
noLink: true,
|
||||||
|
});
|
||||||
|
if (resultIndex === copyErrorAndQuitIndex) {
|
||||||
|
return 'quit';
|
||||||
|
}
|
||||||
|
|
||||||
|
userConfig.set('safeStorageDecryptionError', 'true');
|
||||||
|
return 'continue';
|
||||||
|
}
|
||||||
|
|
||||||
async function initializeSQL(
|
async function initializeSQL(
|
||||||
userDataPath: string
|
userDataPath: string
|
||||||
): Promise<{ ok: true; error: undefined } | { ok: false; error: Error }> {
|
): Promise<{ ok: true; error: undefined } | { ok: false; error: Error }> {
|
||||||
@@ -1818,6 +1873,12 @@ const onDatabaseInitializationError = async (error: Error) => {
|
|||||||
buttons.push(i18n('icu:copyErrorAndQuit'));
|
buttons.push(i18n('icu:copyErrorAndQuit'));
|
||||||
copyErrorAndQuitButtonIndex = 0;
|
copyErrorAndQuitButtonIndex = 0;
|
||||||
defaultButtonId = copyErrorAndQuitButtonIndex;
|
defaultButtonId = copyErrorAndQuitButtonIndex;
|
||||||
|
} else if (error instanceof SafeStorageDecryptionError) {
|
||||||
|
log.error(
|
||||||
|
'onDatabaseInitializationError: SafeStorageDecryptionError, user chose to quit'
|
||||||
|
);
|
||||||
|
app.exit(1);
|
||||||
|
return;
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, this is some other kind of DB error, most likely broken safeStorage key.
|
// Otherwise, this is some other kind of DB error, most likely broken safeStorage key.
|
||||||
// Let's give them the option to delete and show them the support guide.
|
// Let's give them the option to delete and show them the support guide.
|
||||||
|
|||||||
6
ts/types/SafeStorageDecryptionError.ts
Normal file
6
ts/types/SafeStorageDecryptionError.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
// Copyright 2025 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
export class SafeStorageDecryptionError extends Error {
|
||||||
|
override name = 'SafeStorageDecryptionError';
|
||||||
|
}
|
||||||
@@ -21,6 +21,19 @@ function getLinuxName(): string | undefined {
|
|||||||
return match[1];
|
return match[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isFlatpak(): boolean {
|
||||||
|
if (process.env.container === 'flatpak') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const linuxName = getLinuxName();
|
||||||
|
if (linuxName && linuxName.toLowerCase().includes('flatpak')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
function isWaylandEnabled(): boolean {
|
function isWaylandEnabled(): boolean {
|
||||||
return Boolean(process.env.WAYLAND_DISPLAY);
|
return Boolean(process.env.WAYLAND_DISPLAY);
|
||||||
}
|
}
|
||||||
@@ -32,6 +45,7 @@ function isLinuxUsingKDE(): boolean {
|
|||||||
const OS = {
|
const OS = {
|
||||||
...getOSFunctions(os.release()),
|
...getOSFunctions(os.release()),
|
||||||
getLinuxName,
|
getLinuxName,
|
||||||
|
isFlatpak,
|
||||||
isLinuxUsingKDE,
|
isLinuxUsingKDE,
|
||||||
isWaylandEnabled,
|
isWaylandEnabled,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user