Test safeStorage in Flatpak environments

This commit is contained in:
ayumi-signal
2025-09-29 15:58:14 -07:00
committed by GitHub
parent ec7d07269d
commit af55cf4682
4 changed files with 97 additions and 4 deletions

View File

@@ -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."

View File

@@ -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') {
log.info('getSQLKey: removing legacy key'); if (key === legacyKeyValue) {
userConfig.set('key', undefined); // Confirmed roundtrip encryption, we can remove the legacy key
log.info('getSQLKey: removing legacy key');
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);
userConfig.set('key', undefined);
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);
}
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.

View 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';
}

View File

@@ -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,
}; };