From 940dfa99401c04c78bc530c72ecba6f6ccee62e8 Mon Sep 17 00:00:00 2001 From: Michael Weiss Date: Fri, 14 May 2021 02:23:56 +0200 Subject: [PATCH 1/2] signal-desktop: Fix the database encryption by preloading SQLCipher AFAIK this is the only reliable way for us to ensure SQLCipher will be loaded instead of SQLite. It feels like a hack/workaround but according to the SQLCipher developers [0] "this issue can and should be handled downstream at the application level: 1. While it may feel like a workaround, using LD_PRELOAD is a legitimate approach here because it will substitute the system SQLite with SQLCipher which is the intended usage model;". This fixes #108772 for NixOS 20.09 users who upgrade to NixOS 21.05 and replaces #117555. For nixos-unstable users this will unfortunately break everything again so we should add a script to ease the transition (in a separate commit so that we can revert it for NixOS 21.05). [0]: https://github.com/sqlcipher/sqlcipher/issues/385#issuecomment-802874340 --- nixos/tests/signal-desktop.nix | 9 ++++----- .../instant-messengers/signal-desktop/default.nix | 6 ++++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/nixos/tests/signal-desktop.nix b/nixos/tests/signal-desktop.nix index deddb9d0834..42485cd0da7 100644 --- a/nixos/tests/signal-desktop.nix +++ b/nixos/tests/signal-desktop.nix @@ -44,12 +44,11 @@ import ./make-test-python.nix ({ pkgs, ...} : # - 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" ) ''; }) diff --git a/pkgs/applications/networking/instant-messengers/signal-desktop/default.nix b/pkgs/applications/networking/instant-messengers/signal-desktop/default.nix index 50161f1be14..38d52b26bff 100644 --- a/pkgs/applications/networking/instant-messengers/signal-desktop/default.nix +++ b/pkgs/applications/networking/instant-messengers/signal-desktop/default.nix @@ -117,9 +117,15 @@ in stdenv.mkDerivation rec { 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} ) From 45bd7b39a444c904986324b5f7c46ba867612575 Mon Sep 17 00:00:00 2001 From: Michael Weiss Date: Fri, 14 May 2021 21:59:39 +0200 Subject: [PATCH 2/2] signal-desktop: Add a Python wrapper to re-encrypt DBs This is super hacky... :o But then again it should at least prevent data loss. Note: At least this isn't required for NixOS 21.05. --- .../signal-desktop/db-reencryption-wrapper.py | 92 +++++++++++++++++++ .../signal-desktop/default.nix | 15 ++- 2 files changed, 106 insertions(+), 1 deletion(-) create mode 100755 pkgs/applications/networking/instant-messengers/signal-desktop/db-reencryption-wrapper.py diff --git a/pkgs/applications/networking/instant-messengers/signal-desktop/db-reencryption-wrapper.py b/pkgs/applications/networking/instant-messengers/signal-desktop/db-reencryption-wrapper.py new file mode 100755 index 00000000000..8556ee1e4d7 --- /dev/null +++ b/pkgs/applications/networking/instant-messengers/signal-desktop/db-reencryption-wrapper.py @@ -0,0 +1,92 @@ +#!@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() diff --git a/pkgs/applications/networking/instant-messengers/signal-desktop/default.nix b/pkgs/applications/networking/instant-messengers/signal-desktop/default.nix index 38d52b26bff..e1ba5884de7 100644 --- a/pkgs/applications/networking/instant-messengers/signal-desktop/default.nix +++ b/pkgs/applications/networking/instant-messengers/signal-desktop/default.nix @@ -10,6 +10,9 @@ , 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 @@ -112,7 +115,7 @@ in stdenv.mkDerivation rec { # 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 ''; @@ -137,6 +140,16 @@ in stdenv.mkDerivation rec { 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": passthru.tests.application-launch = nixosTests.signal-desktop;