Merge pull request #122926 from primeos/signal-desktop-fix-db-encryption

signal-desktop: Fix the database encryption by preloading SQLCipher

Changed files
+116 -6
nixos
pkgs
applications
networking
instant-messengers
+4 -5
nixos/tests/signal-desktop.nix
···
# - https://github.com/NixOS/nixpkgs/issues/108772
# - https://github.com/NixOS/nixpkgs/pull/117555
print(machine.succeed("su - alice -c 'file ~/.config/Signal/sql/db.sqlite'"))
-
# TODO: The DB should be encrypted and the following should be machine.fail
-
# instead of machine.succeed but the DB is currently unencrypted and we
-
# want to notice if this isn't the case anymore as the transition to a
-
# encrypted DB can cause data loss!:
machine.succeed(
-
"su - alice -c 'file ~/.config/Signal/sql/db.sqlite' | grep -i sqlite"
+
"su - alice -c 'file ~/.config/Signal/sql/db.sqlite' | grep 'db.sqlite: data'"
+
)
+
machine.fail(
+
"su - alice -c 'file ~/.config/Signal/sql/db.sqlite' | grep -e SQLite -e database"
)
'';
})
+92
pkgs/applications/networking/instant-messengers/signal-desktop/db-reencryption-wrapper.py
···
+
#!@PYTHON@
+
+
import json
+
import os
+
import re
+
import shlex
+
import sqlite3
+
import subprocess
+
import sys
+
+
+
DB_PATH = os.path.join(os.environ['HOME'], '.config/Signal/sql/db.sqlite')
+
DB_COPY = os.path.join(os.environ['HOME'], '.config/Signal/sql/db.tmp')
+
CONFIG_PATH = os.path.join(os.environ['HOME'], '.config/Signal/config.json')
+
+
+
def zenity_askyesno(title, text):
+
args = [
+
'@ZENITY@',
+
'--question',
+
'--title',
+
shlex.quote(title),
+
'--text',
+
shlex.quote(text)
+
]
+
return subprocess.run(args).returncode == 0
+
+
+
def start_signal():
+
os.execvp('@SIGNAL-DESKTOP@', ['@SIGNAL-DESKTOP@'] + sys.argv[1:])
+
+
+
def copy_pragma(name):
+
result = subprocess.run([
+
'@SQLCIPHER@',
+
DB_PATH,
+
f"PRAGMA {name};"
+
], check=True, capture_output=True).stdout
+
result = re.search(r'[0-9]+', result.decode()).group(0)
+
subprocess.run([
+
'@SQLCIPHER@',
+
DB_COPY,
+
f"PRAGMA key = \"x'{key}'\"; PRAGMA {name} = {result};"
+
], check=True, capture_output=True)
+
+
+
try:
+
# Test if DB is encrypted:
+
con = sqlite3.connect(f'file:{DB_PATH}?mode=ro', uri=True)
+
cursor = con.cursor()
+
cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
+
con.close()
+
except:
+
# DB is encrypted, everything ok:
+
start_signal()
+
+
+
# DB is unencrypted!
+
answer = zenity_askyesno(
+
"Error: Signal-Desktop database is not encrypted",
+
"Should we try to fix this automatically?"
+
+ "You likely want to backup ~/.config/Signal/ first."
+
)
+
if not answer:
+
answer = zenity_askyesno(
+
"Launch Signal-Desktop",
+
"DB is unencrypted, should we still launch Signal-Desktop?"
+
+ "Warning: This could result in data loss!"
+
)
+
if not answer:
+
print('Aborted')
+
sys.exit(0)
+
start_signal()
+
+
# Re-encrypt the DB:
+
with open(CONFIG_PATH) as json_file:
+
key = json.load(json_file)['key']
+
result = subprocess.run([
+
'@SQLCIPHER@',
+
DB_PATH,
+
f" ATTACH DATABASE '{DB_COPY}' AS signal_db KEY \"x'{key}'\";"
+
+ " SELECT sqlcipher_export('signal_db');"
+
+ " DETACH DATABASE signal_db;"
+
]).returncode
+
if result != 0:
+
print('DB encryption failed')
+
sys.exit(1)
+
# Need to copy user_version and schema_version manually:
+
copy_pragma('user_version')
+
copy_pragma('schema_version')
+
os.rename(DB_COPY, DB_PATH)
+
start_signal()
+20 -1
pkgs/applications/networking/instant-messengers/signal-desktop/default.nix
···
, hunspellDicts, spellcheckerLanguage ? null # E.g. "de_DE"
# For a full list of available languages:
# $ cat pkgs/development/libraries/hunspell/dictionaries.nix | grep "dictFileName =" | awk '{ print $3 }'
+
, python3
+
, gnome
+
, sqlcipher
}:
let
···
# Symlink to bin
mkdir -p $out/bin
-
ln -s $out/lib/Signal/signal-desktop $out/bin/signal-desktop
+
ln -s $out/lib/Signal/signal-desktop $out/bin/signal-desktop-unwrapped
runHook postInstall
'';
+
+
# Required for $SQLCIPHER_LIB which contains "/build/" inside the path:
+
noAuditTmpdir = true;
preFixup = ''
+
export SQLCIPHER_LIB="$out/lib/Signal/resources/app.asar.unpacked/node_modules/better-sqlite3/build/Release/better_sqlite3.node"
+
test -x "$SQLCIPHER_LIB" # To ensure the location hasn't changed
gappsWrapperArgs+=(
--prefix LD_LIBRARY_PATH : "${lib.makeLibraryPath [ stdenv.cc.cc ] }"
+
--prefix LD_PRELOAD : "$SQLCIPHER_LIB"
${customLanguageWrapperArgs}
)
···
autoPatchelf --no-recurse -- $out/lib/Signal/
patchelf --add-needed ${libpulseaudio}/lib/libpulse.so $out/lib/Signal/resources/app.asar.unpacked/node_modules/ringrtc/build/linux/libringrtc.node
+
'';
+
+
postFixup = ''
+
# This hack is temporarily required to avoid data-loss for users:
+
cp ${./db-reencryption-wrapper.py} $out/bin/signal-desktop
+
substituteInPlace $out/bin/signal-desktop \
+
--replace '@PYTHON@' '${python3}/bin/python3' \
+
--replace '@ZENITY@' '${gnome.zenity}/bin/zenity' \
+
--replace '@SQLCIPHER@' '${sqlcipher}/bin/sqlcipher' \
+
--replace '@SIGNAL-DESKTOP@' "$out/bin/signal-desktop-unwrapped"
'';
# Tests if the application launches and waits for "Link your phone to Signal Desktop":