Skip to content

Instantly share code, notes, and snippets.

@aguynamedben
Last active December 9, 2021 09:15
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aguynamedben/14253e34bc7e0a881d99c8e45eb45a47 to your computer and use it in GitHub Desktop.
Save aguynamedben/14253e34bc7e0a881d99c8e45eb45a47 to your computer and use it in GitHub Desktop.
Setting up SQLCipher with node-sqlite3 and Electron

Gist notes

How I got SQLCipher working with Electron on macOS:

  • First compile SQLCipher and dynamically link it to macOS' crypto libraries. Don't use OpenSSL. Zetetic (makers of SQLCipher) say that using macOS' crypto libraries is definitely going to be easiest on a Mac. Note that using SQLCipher libraries installed via Homebrew is going to seem like it works, and it will on your computer, but Homebrew's SQLCipher dynamcially links OpenSSL, so your app will crash when deployed to other computers (i.e. dynamic linking will fail at run timer because other computer wont have OpenSSL installed). So you want to dynamically link macOS crypto.
  • Then you want to rebuild node-sqlite3 with a modified binding.gyp so that the SQLCipher library is statically linked into your SQLite binding. You should end up with a binary file ./node_modules/sqlite3/lib/binding/electron-v3.0-darwin-x64/node_sqlite3.node that has libsqlcipher.a statically linked within it. The statically linked libsqlcipher.a will in-turn dynamically link macOS' libcrypto.
  • So in summary: dynamically link macOS' libcrypto when compiling SQLCipher, and statically link libsqlcipher when compiling (a.k.a. rebuilding) node-sqlite3 bindings.
  • Also be sure you're using the Electron flags (check version!) for rebuilding. (see RebuildSQLite.sh below).
  • The core things I didn't understand and that slowed me down:
    • Never having dealt with rebuilding native node modules. node-gyp and electron-rebuild are used to do this.
    • Not understanding dynamic and static linking well enough. This was especially challenging because crashes are just hard crashes with horrible error messages, and this setup requires multiple steps in linking (once when you compile SQLCipher and once when you rebuild node-sqlite3).

SQLCipher

  1. Clone sqlcipher.

    cd ~/code git clone git@github.com:sqlcipher/sqlcipher.git cd sqlcipher git checkout -b v.3.4.2 tags/v3.4.2

  2. Configure the build.

    make clean ./configure --enable-tempstore=yes --enable-load-extension --disable-tcl --with-crypto-lib=commoncrypto
    CFLAGS="-DSQLITE_HAS_CODEC -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_FTS3_PARENTHESIS -DSQLITE_ENABLE_FTS5"
    LDFLAGS="-framework Security -framework Foundation"

(was -framework CoreServices instead of -framework Foundation at one point)

  1. Run the build.

    make

This will produce the file ./.lib/libsqlcipher.a which will be referenced when we run npm rebuild. If you want to clean up the built files, run make clean.

Other notes on compiling SQLCipher

I've spent countless hours trying to get SQLCipher to work by statically linking OpenSSL's libcrypto.a. Building libsqlcipher.a seems to work, and so does npm rebuilding node-sqlite3, but when Command E is deployed everything seems to work, but the resulting database is just NOT encrypted. It seems there may be some problem or core thing I'm missing with how statically linking OpenSSL to SQLCipher works. ¯_(ツ)_/¯

  1. Rebuild node-sqlite3 pointing to libsqlcipher.a

This is done in our script tesla/sqlcipher/RebuildSQLite.sh. You can just run yarn rebuild-sqlite.

echo "Copying custom binding.gyp (node-sqlite3)"
cp sqlcipher/custom-binding.gyp node_modules/sqlite3/binding.gyp

echo "Rebuilding node-sqlite3 bindings with statically linked SQLCipher (libsqlcipher.a)"
npm rebuild sqlite3 --build-from-source --static_linking_path=/Users/ben/code/sqlcipher/.libs/libsqlcipher.a --runtime=electron --target=3.0.6 --dist-url=https://atom.io/download/electron

libsqlcipher.a is pulled into the node-sqlite3 bindings and a new binary file is placed at ./node_modules/sqlite3/lib/binding/electron-v3.0-darwin-x64/node_sqlite3.node. This binary has SQLCipher bundled in it, which in-turn dynamically links Common Crypto. Running yarn package from here will build the app with the SQLCipher features enabled.

Debugging commands

otool -L /Users/ben/code/sqlcipher/.libs/libsqlcipher.dylib

strings .libs/libsqlcipher.a | grep -i RAND_add
strings .libs/libsqlcipher.a | grep -i RAND_add

What goes wrong

  • Beware of accidental dynamic includes or configuration issues in binding.gyp.
# WORKS
{
"includes": [ "deps/common-sqlite.gypi" ],
"variables": {
},
"targets": [
{
"target_name": "<(module_name)",
"include_dirs": [
"<!(node -e \"require('nan')\")",
# NOTE: This is hard-coded, you need to change accordingly.
"/Users/ben/code/sqlcipher"
],
"libraries": [
# NOTE: This is hard-coded, you need to change accordingly.
"/Users/ben/code/sqlcipher/.libs/libsqlcipher.a"
],
"sources": [
"src/database.cc",
"src/node_sqlite3.cc",
"src/statement.cc"
]
},
{
"target_name": "action_after_build",
"type": "none",
"dependencies": [ "<(module_name)" ],
"copies": [
{
"files": [ "<(PRODUCT_DIR)/<(module_name).node" ],
"destination": "<(module_path)"
}
]
}
]
}
#!/bin/bash
set -e
echo "Copying custom binding.gyp (node-sqlite3)"
cp sqlcipher/custom-binding.gyp node_modules/sqlite3/binding.gyp
echo "Rebuilding node-sqlite3 bindings with SQLCipher"
npm rebuild sqlite3 --build-from-source --runtime=electron --target=3.0.6 --dist-url=https://atom.io/download/electron
# I don't know why building node-sqlite3 for Node.js isn't working... trying to
# use sqlite3 from Node.js (not Electron) fails with
# Symbol not found: _kSecRandomDefault
# We don't actually need it to work, except for tests maybe...
#
# echo "Rebuilding node-sqlite3 bindings for Node.js with statically linked SQLCipher (libsqlcipher.a)"
# npm rebuild sqlite3 --build-from-source --runtime=node --target=10.13.0
@NKnife
Copy link

NKnife commented Mar 12, 2019

Thank you very much, you save my time!

@ajmasia
Copy link

ajmasia commented Jul 14, 2020

Hi @aguynamedben, I'm trying to compile SQLcipher to use it with electron 8.1.0 and following these steps I get some erros.

Currently, SQLcipher is on 4.4.0 version. These are my steps:

Clone the SQLCipher repo:

make clear
./configure --enable-tempstore=yes --enable-load-extension --disable-tcl --with-crypto-lib=commoncrypto CFLAGS="-DSQLITE_HAS_CODEC -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_FTS3_PARENTHESIS -DSQLITE_ENABLE_FTS5" LDFLAGS="-framework Security -framework CoreServices
make
cp ~/sqlcipher/custom-binding.gyp node_modules/sqlite3/binding.gyp
# In my repo
rm -rf node+modules
npm i 
# Using sqlite3@4.2.0
npm rebuild sqlite3 --build-from-source --runtime=electron --target=8.1.0 --dist-url=https://electronjs.org/headers

custom-binding.gyp

{
  "includes": [ "deps/common-sqlite.gypi" ],
  "variables": {
  },
  "targets": [
    {
      "target_name": "<(module_name)",
      "include_dirs": [
        "<!(node -e \"require('nan')\")",
        # NOTE: This is hard-coded, you need to change accordingly.
        "~/sqlcipher"
      ],
      "libraries": [
        # NOTE: This is hard-coded, you need to change accordingly.
        "~/sqlcipher/.libs/libsqlcipher.a"
      ],
      "sources": [
        "src/database.cc",
        "src/node_sqlite3.cc",
        "src/statement.cc"
      ]
    },
    {
      "target_name": "action_after_build",
      "type": "none",
      "dependencies": [ "<(module_name)" ],
      "copies": [
          {
            "files": [ "<(PRODUCT_DIR)/<(module_name).node" ],
            "destination": "<(module_path)"
          }
      ]
    }
  ]
}

And get this errors:

> sqlite3@4.2.0 install /Users/.../node_modules/sqlite3
> node-pre-gyp install --fallback-to-build

node-pre-gyp WARN Using request for node-pre-gyp https download
  CXX(target) Release/obj.target/node_sqlite3/src/database.o
../src/database.cc:49:17: warning: 'MakeCallback' is deprecated [-Wdeprecated-declarations]
                TRY_CATCH_CALL(this->handle(), cb, 1, argv);
                ^
../src/macros.h:112:10: note: expanded from macro 'TRY_CATCH_CALL'
    Nan::MakeCallback((context), (callback), (argc), (argv))
         ^
../../nan/nan.h:1026:3: note: 'MakeCallback' has been explicitly marked deprecated here
  NAN_DEPRECATED inline v8::Local<v8::Value> MakeCallback(
  ^
../../nan/nan.h:106:40: note: expanded from macro 'NAN_DEPRECATED'
# define NAN_DEPRECATED __attribute__((deprecated))
                                       ^
../src/database.cc:63:13: warning: 'MakeCallback' is deprecated [-Wdeprecated-declarations]
            EMIT_EVENT(handle(), 2, info);
            ^
../src/macros.h:105:5: note: expanded from macro 'EMIT_EVENT'
    TRY_CATCH_CALL((obj),                                                      \
    ^
../src/macros.h:112:10: note: expanded from macro 'TRY_CATCH_CALL'
    Nan::MakeCallback((context), (callback), (argc), (argv))
         ^
../../nan/nan.h:1026:3: note: 'MakeCallback' has been explicitly marked deprecated here
  NAN_DEPRECATED inline v8::Local<v8::Value> MakeCallback(
  ^
../../nan/nan.h:106:40: note: expanded from macro 'NAN_DEPRECATED'
# define NAN_DEPRECATED __attribute__((deprecated))
                                       ^
../src/database.cc:92:13: warning: 'MakeCallback' is deprecated [-Wdeprecated-declarations]
            TRY_CATCH_CALL(handle(), cb, 1, argv);
            ^
../src/macros.h:112:10: note: expanded from macro 'TRY_CATCH_CALL'
    Nan::MakeCallback((context), (callback), (argc), (argv))
         ^
../../nan/nan.h:1026:3: note: 'MakeCallback' has been explicitly marked deprecated here
  NAN_DEPRECATED inline v8::Local<v8::Value> MakeCallback(
  ^
../../nan/nan.h:106:40: note: expanded from macro 'NAN_DEPRECATED'
# define NAN_DEPRECATED __attribute__((deprecated))
                                       ^
../src/database.cc:96:13: warning: 'MakeCallback' is deprecated [-Wdeprecated-declarations]
            EMIT_EVENT(handle(), 2, argv);
            ^
../src/macros.h:105:5: note: expanded from macro 'EMIT_EVENT'
    TRY_CATCH_CALL((obj),                                                      \
    ^
../src/macros.h:112:10: note: expanded from macro 'TRY_CATCH_CALL'
    Nan::MakeCallback((context), (callback), (argc), (argv))
         ^
../../nan/nan.h:1026:3: note: 'MakeCallback' has been explicitly marked deprecated here
  NAN_DEPRECATED inline v8::Local<v8::Value> MakeCallback(
  ^
../../nan/nan.h:106:40: note: expanded from macro 'NAN_DEPRECATED'
# define NAN_DEPRECATED __attribute__((deprecated))
                                       ^
../src/database.cc:133:10: warning: 'ForceSet' is deprecated [-Wdeprecated-declarations]
    Nan::ForceSet(info.This(), Nan::New("filename").ToLocalChecked(), info[0].As<String>(), ReadOnly);
         ^
../../nan/nan_maybe_43_inl.h:117:1: note: 'ForceSet' has been explicitly marked deprecated here
NAN_DEPRECATED inline Maybe<bool> ForceSet(
^
../../nan/nan.h:106:40: note: expanded from macro 'NAN_DEPRECATED'
# define NAN_DEPRECATED __attribute__((deprecated))
                                       ^
../src/database.cc:134:10: warning: 'ForceSet' is deprecated [-Wdeprecated-declarations]
    Nan::ForceSet(info.This(), Nan::New("mode").ToLocalChecked(), Nan::New(mode), ReadOnly);
         ^
../../nan/nan_maybe_43_inl.h:117:1: note: 'ForceSet' has been explicitly marked deprecated here
NAN_DEPRECATED inline Maybe<bool> ForceSet(
^
../../nan/nan.h:106:40: note: expanded from macro 'NAN_DEPRECATED'
# define NAN_DEPRECATED __attribute__((deprecated))
                                       ^
../src/database.cc:144:9: warning: unused variable 'status' [-Wunused-variable]
    int status = uv_queue_work(uv_default_loop(),
        ^
../src/database.cc:190:9: warning: 'MakeCallback' is deprecated [-Wdeprecated-declarations]
        TRY_CATCH_CALL(db->handle(), cb, 1, argv);
        ^
../src/macros.h:112:10: note: expanded from macro 'TRY_CATCH_CALL'
    Nan::MakeCallback((context), (callback), (argc), (argv))
         ^
../../nan/nan.h:1026:3: note: 'MakeCallback' has been explicitly marked deprecated here
  NAN_DEPRECATED inline v8::Local<v8::Value> MakeCallback(
  ^
../../nan/nan.h:106:40: note: expanded from macro 'NAN_DEPRECATED'
# define NAN_DEPRECATED __attribute__((deprecated))
                                       ^
../src/database.cc:194:9: warning: 'MakeCallback' is deprecated [-Wdeprecated-declarations]
        EMIT_EVENT(db->handle(), 2, info);
        ^
../src/macros.h:105:5: note: expanded from macro 'EMIT_EVENT'
    TRY_CATCH_CALL((obj),                                                      \
    ^
../src/macros.h:112:10: note: expanded from macro 'TRY_CATCH_CALL'
    Nan::MakeCallback((context), (callback), (argc), (argv))
         ^
../../nan/nan.h:1026:3: note: 'MakeCallback' has been explicitly marked deprecated here
  NAN_DEPRECATED inline v8::Local<v8::Value> MakeCallback(
  ^
../../nan/nan.h:106:40: note: expanded from macro 'NAN_DEPRECATED'
# define NAN_DEPRECATED __attribute__((deprecated))
                                       ^
../src/database.cc:199:9: warning: 'MakeCallback' is deprecated [-Wdeprecated-declarations]
        EMIT_EVENT(db->handle(), 1, info);
        ^
../src/macros.h:105:5: note: expanded from macro 'EMIT_EVENT'
    TRY_CATCH_CALL((obj),                                                      \
    ^
../src/macros.h:112:10: note: expanded from macro 'TRY_CATCH_CALL'
    Nan::MakeCallback((context), (callback), (argc), (argv))
         ^
../../nan/nan.h:1026:3: note: 'MakeCallback' has been explicitly marked deprecated here
  NAN_DEPRECATED inline v8::Local<v8::Value> MakeCallback(
  ^
../../nan/nan.h:106:40: note: expanded from macro 'NAN_DEPRECATED'
# define NAN_DEPRECATED __attribute__((deprecated))
                                       ^
../src/database.cc:230:9: warning: unused variable 'status' [-Wunused-variable]
    int status = uv_queue_work(uv_default_loop(),
        ^
../src/database.cc:273:9: warning: 'MakeCallback' is deprecated [-Wdeprecated-declarations]
        TRY_CATCH_CALL(db->handle(), cb, 1, argv);
        ^
../src/macros.h:112:10: note: expanded from macro 'TRY_CATCH_CALL'
    Nan::MakeCallback((context), (callback), (argc), (argv))
         ^
../../nan/nan.h:1026:3: note: 'MakeCallback' has been explicitly marked deprecated here
  NAN_DEPRECATED inline v8::Local<v8::Value> MakeCallback(
  ^
../../nan/nan.h:106:40: note: expanded from macro 'NAN_DEPRECATED'
# define NAN_DEPRECATED __attribute__((deprecated))
                                       ^
../src/database.cc:277:9: warning: 'MakeCallback' is deprecated [-Wdeprecated-declarations]
        EMIT_EVENT(db->handle(), 2, info);
        ^
../src/macros.h:105:5: note: expanded from macro 'EMIT_EVENT'
    TRY_CATCH_CALL((obj),                                                      \
    ^
../src/macros.h:112:10: note: expanded from macro 'TRY_CATCH_CALL'
    Nan::MakeCallback((context), (callback), (argc), (argv))
         ^
../../nan/nan.h:1026:3: note: 'MakeCallback' has been explicitly marked deprecated here
  NAN_DEPRECATED inline v8::Local<v8::Value> MakeCallback(
  ^
../../nan/nan.h:106:40: note: expanded from macro 'NAN_DEPRECATED'
# define NAN_DEPRECATED __attribute__((deprecated))
                                       ^
../src/database.cc:282:9: warning: 'MakeCallback' is deprecated [-Wdeprecated-declarations]
        EMIT_EVENT(db->handle(), 1, info);
        ^
../src/macros.h:105:5: note: expanded from macro 'EMIT_EVENT'
    TRY_CATCH_CALL((obj),                                                      \
    ^
../src/macros.h:112:10: note: expanded from macro 'TRY_CATCH_CALL'
    Nan::MakeCallback((context), (callback), (argc), (argv))
         ^
../../nan/nan.h:1026:3: note: 'MakeCallback' has been explicitly marked deprecated here
  NAN_DEPRECATED inline v8::Local<v8::Value> MakeCallback(
  ^
../../nan/nan.h:106:40: note: expanded from macro 'NAN_DEPRECATED'
# define NAN_DEPRECATED __attribute__((deprecated))
                                       ^
../src/database.cc:297:9: warning: 'MakeCallback' is deprecated [-Wdeprecated-declarations]
        TRY_CATCH_CALL(info.This(), callback, 0, NULL);
        ^
../src/macros.h:112:10: note: expanded from macro 'TRY_CATCH_CALL'
    Nan::MakeCallback((context), (callback), (argc), (argv))
         ^
../../nan/nan.h:1026:3: note: 'MakeCallback' has been explicitly marked deprecated here
  NAN_DEPRECATED inline v8::Local<v8::Value> MakeCallback(
  ^
../../nan/nan.h:106:40: note: expanded from macro 'NAN_DEPRECATED'
# define NAN_DEPRECATED __attribute__((deprecated))
                                       ^
../src/database.cc:314:9: warning: 'MakeCallback' is deprecated [-Wdeprecated-declarations]
        TRY_CATCH_CALL(info.This(), callback, 0, NULL);
        ^
../src/macros.h:112:10: note: expanded from macro 'TRY_CATCH_CALL'
    Nan::MakeCallback((context), (callback), (argc), (argv))
         ^
../../nan/nan.h:1026:3: note: 'MakeCallback' has been explicitly marked deprecated here
  NAN_DEPRECATED inline v8::Local<v8::Value> MakeCallback(
  ^
../../nan/nan.h:106:40: note: expanded from macro 'NAN_DEPRECATED'
# define NAN_DEPRECATED __attribute__((deprecated))
                                       ^
../src/database.cc:421:5: warning: 'MakeCallback' is deprecated [-Wdeprecated-declarations]
    EMIT_EVENT(db->handle(), 2, argv);
    ^
../src/macros.h:105:5: note: expanded from macro 'EMIT_EVENT'
    TRY_CATCH_CALL((obj),                                                      \
    ^
../src/macros.h:112:10: note: expanded from macro 'TRY_CATCH_CALL'
    Nan::MakeCallback((context), (callback), (argc), (argv))
         ^
../../nan/nan.h:1026:3: note: 'MakeCallback' has been explicitly marked deprecated here
  NAN_DEPRECATED inline v8::Local<v8::Value> MakeCallback(
  ^
../../nan/nan.h:106:40: note: expanded from macro 'NAN_DEPRECATED'
# define NAN_DEPRECATED __attribute__((deprecated))
                                       ^
../src/database.cc:462:5: warning: 'MakeCallback' is deprecated [-Wdeprecated-declarations]
    EMIT_EVENT(db->handle(), 3, argv);
    ^
../src/macros.h:105:5: note: expanded from macro 'EMIT_EVENT'
    TRY_CATCH_CALL((obj),                                                      \
    ^
../src/macros.h:112:10: note: expanded from macro 'TRY_CATCH_CALL'
    Nan::MakeCallback((context), (callback), (argc), (argv))
         ^
../../nan/nan.h:1026:3: note: 'MakeCallback' has been explicitly marked deprecated here
  NAN_DEPRECATED inline v8::Local<v8::Value> MakeCallback(
  ^
../../nan/nan.h:106:40: note: expanded from macro 'NAN_DEPRECATED'
# define NAN_DEPRECATED __attribute__((deprecated))
                                       ^
../src/database.cc:507:5: warning: 'MakeCallback' is deprecated [-Wdeprecated-declarations]
    EMIT_EVENT(db->handle(), 4, argv);
    ^
../src/macros.h:105:5: note: expanded from macro 'EMIT_EVENT'
    TRY_CATCH_CALL((obj),                                                      \
    ^
../src/macros.h:112:10: note: expanded from macro 'TRY_CATCH_CALL'
    Nan::MakeCallback((context), (callback), (argc), (argv))
         ^
../../nan/nan.h:1026:3: note: 'MakeCallback' has been explicitly marked deprecated here
  NAN_DEPRECATED inline v8::Local<v8::Value> MakeCallback(
  ^
../../nan/nan.h:106:40: note: expanded from macro 'NAN_DEPRECATED'
# define NAN_DEPRECATED __attribute__((deprecated))
                                       ^
../src/database.cc:528:9: warning: unused variable 'status' [-Wunused-variable]
    int status = uv_queue_work(uv_default_loop(),
        ^
../src/database.cc:564:13: warning: 'MakeCallback' is deprecated [-Wdeprecated-declarations]
            TRY_CATCH_CALL(db->handle(), cb, 1, argv);
            ^
../src/macros.h:112:10: note: expanded from macro 'TRY_CATCH_CALL'
    Nan::MakeCallback((context), (callback), (argc), (argv))
         ^
../../nan/nan.h:1026:3: note: 'MakeCallback' has been explicitly marked deprecated here
  NAN_DEPRECATED inline v8::Local<v8::Value> MakeCallback(
  ^
../../nan/nan.h:106:40: note: expanded from macro 'NAN_DEPRECATED'
# define NAN_DEPRECATED __attribute__((deprecated))
                                       ^
../src/database.cc:568:13: warning: 'MakeCallback' is deprecated [-Wdeprecated-declarations]
            EMIT_EVENT(db->handle(), 2, info);
            ^
../src/macros.h:105:5: note: expanded from macro 'EMIT_EVENT'
    TRY_CATCH_CALL((obj),                                                      \
    ^
../src/macros.h:112:10: note: expanded from macro 'TRY_CATCH_CALL'
    Nan::MakeCallback((context), (callback), (argc), (argv))
         ^
../../nan/nan.h:1026:3: note: 'MakeCallback' has been explicitly marked deprecated here
  NAN_DEPRECATED inline v8::Local<v8::Value> MakeCallback(
  ^
../../nan/nan.h:106:40: note: expanded from macro 'NAN_DEPRECATED'
# define NAN_DEPRECATED __attribute__((deprecated))
                                       ^
../src/database.cc:573:9: warning: 'MakeCallback' is deprecated [-Wdeprecated-declarations]
        TRY_CATCH_CALL(db->handle(), cb, 1, argv);
        ^
../src/macros.h:112:10: note: expanded from macro 'TRY_CATCH_CALL'
    Nan::MakeCallback((context), (callback), (argc), (argv))
         ^
../../nan/nan.h:1026:3: note: 'MakeCallback' has been explicitly marked deprecated here
  NAN_DEPRECATED inline v8::Local<v8::Value> MakeCallback(
  ^
../../nan/nan.h:106:40: note: expanded from macro 'NAN_DEPRECATED'
# define NAN_DEPRECATED __attribute__((deprecated))
                                       ^
../src/database.cc:603:9: warning: 'MakeCallback' is deprecated [-Wdeprecated-declarations]
        TRY_CATCH_CALL(baton->db->handle(), cb, 1, argv);
        ^
../src/macros.h:112:10: note: expanded from macro 'TRY_CATCH_CALL'
    Nan::MakeCallback((context), (callback), (argc), (argv))
         ^
../../nan/nan.h:1026:3: note: 'MakeCallback' has been explicitly marked deprecated here
  NAN_DEPRECATED inline v8::Local<v8::Value> MakeCallback(
  ^
../../nan/nan.h:106:40: note: expanded from macro 'NAN_DEPRECATED'
# define NAN_DEPRECATED __attribute__((deprecated))
                                       ^
../src/database.cc:628:9: warning: unused variable 'status' [-Wunused-variable]
    int status = uv_queue_work(uv_default_loop(),
        ^
../src/database.cc:636:5: error: use of undeclared identifier 'sqlite3_enable_load_extension'
    sqlite3_enable_load_extension(baton->db->_handle, 1);
    ^
../src/database.cc:639:21: error: use of undeclared identifier 'sqlite3_load_extension'
    baton->status = sqlite3_load_extension(
                    ^
../src/database.cc:646:5: error: use of undeclared identifier 'sqlite3_enable_load_extension'
    sqlite3_enable_load_extension(baton->db->_handle, 0);
    ^
../src/database.cc:666:13: warning: 'MakeCallback' is deprecated [-Wdeprecated-declarations]
            TRY_CATCH_CALL(db->handle(), cb, 1, argv);
            ^
../src/macros.h:112:10: note: expanded from macro 'TRY_CATCH_CALL'
    Nan::MakeCallback((context), (callback), (argc), (argv))
         ^
../../nan/nan.h:1026:3: note: 'MakeCallback' has been explicitly marked deprecated here
  NAN_DEPRECATED inline v8::Local<v8::Value> MakeCallback(
  ^
../../nan/nan.h:106:40: note: expanded from macro 'NAN_DEPRECATED'
# define NAN_DEPRECATED __attribute__((deprecated))
                                       ^
../src/database.cc:670:13: warning: 'MakeCallback' is deprecated [-Wdeprecated-declarations]
            EMIT_EVENT(db->handle(), 2, info);
            ^
../src/macros.h:105:5: note: expanded from macro 'EMIT_EVENT'
    TRY_CATCH_CALL((obj),                                                      \
    ^
../src/macros.h:112:10: note: expanded from macro 'TRY_CATCH_CALL'
    Nan::MakeCallback((context), (callback), (argc), (argv))
         ^
../../nan/nan.h:1026:3: note: 'MakeCallback' has been explicitly marked deprecated here
  NAN_DEPRECATED inline v8::Local<v8::Value> MakeCallback(
  ^
../../nan/nan.h:106:40: note: expanded from macro 'NAN_DEPRECATED'
# define NAN_DEPRECATED __attribute__((deprecated))
                                       ^
../src/database.cc:675:9: warning: 'MakeCallback' is deprecated [-Wdeprecated-declarations]
        TRY_CATCH_CALL(db->handle(), cb, 1, argv);
        ^
../src/macros.h:112:10: note: expanded from macro 'TRY_CATCH_CALL'
    Nan::MakeCallback((context), (callback), (argc), (argv))
         ^
../../nan/nan.h:1026:3: note: 'MakeCallback' has been explicitly marked deprecated here
  NAN_DEPRECATED inline v8::Local<v8::Value> MakeCallback(
  ^
../../nan/nan.h:106:40: note: expanded from macro 'NAN_DEPRECATED'
# define NAN_DEPRECATED __attribute__((deprecated))
                                       ^
28 warnings and 3 errors generated.
make: *** [Release/obj.target/node_sqlite3/src/database.o] Error 1
gyp ERR! build error
gyp ERR! stack Error: `make` failed with exit code: 2
gyp ERR! stack     at ChildProcess.onExit (/Users/.../.nvm/versions/node/v10.19.0/lib/node_modules/npm/node_modules/node-gyp/lib/build.js:191:23)
gyp ERR! stack     at ChildProcess.emit (events.js:198:13)
gyp ERR! stack     at Process.ChildProcess._handle.onexit (internal/child_process.js:248:12)
gyp ERR! System Darwin 19.5.0
gyp ERR! command "/Users/.../.nvm/versions/node/v10.19.0/bin/node" "/Users/.../.nvm/versions/node/v10.19.0/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js" "build" "--fallback-to-build" "--module=/Users/.../node_modules/sqlite3/lib/binding/electron-v8.1-darwin-x64/node_sqlite3.node" "--module_name=node_sqlite3" "--module_path=/Users/.../node_modules/sqlite3/lib/binding/electron-v8.1-darwin-x64" "--napi_version=5" "--node_abi_napi=napi" "--napi_build_version=0" "--node_napi_label=electron-v8.1"
gyp ERR! cwd /Users/.../node_modules/sqlite3
gyp ERR! node -v v10.19.0
gyp ERR! node-gyp -v v5.0.5
gyp ERR! not ok
node-pre-gyp ERR! build error
node-pre-gyp ERR! stack Error: Failed to execute '/Users/.../.nvm/versions/node/v10.19.0/bin/node /Users/.../.nvm/versions/node/v10.19.0/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js build --fallback-to-build --module=/Users/.../node_modules/sqlite3/lib/binding/electron-v8.1-darwin-x64/node_sqlite3.node --module_name=node_sqlite3 --module_path=/Users/.../node_modules/sqlite3/lib/binding/electron-v8.1-darwin-x64 --napi_version=5 --node_abi_napi=napi --napi_build_version=0 --node_napi_label=electron-v8.1' (1)
node-pre-gyp ERR! stack     at ChildProcess.<anonymous> (/Users/.../node_modules/node-pre-gyp/lib/util/compile.js:83:29)
node-pre-gyp ERR! stack     at ChildProcess.emit (events.js:198:13)
node-pre-gyp ERR! stack     at maybeClose (internal/child_process.js:982:16)
node-pre-gyp ERR! stack     at Process.ChildProcess._handle.onexit (internal/child_process.js:259:5)
node-pre-gyp ERR! System Darwin 19.5.0
node-pre-gyp ERR! command "/Users/.../.nvm/versions/node/v10.19.0/bin/node" "/Users/.../node_modules/.bin/node-pre-gyp" "install" "--fallback-to-build"
node-pre-gyp ERR! cwd /Users/.../node_modules/sqlite3
node-pre-gyp ERR! node -v v10.19.0
node-pre-gyp ERR! node-pre-gyp -v v0.11.0
node-pre-gyp ERR! not ok
Failed to execute '/Users/.../.nvm/versions/node/v10.19.0/bin/node /Users/.../.nvm/versions/node/v10.19.0/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js build --fallback-to-build --module=/Users/.../node_modules/sqlite3/lib/binding/electron-v8.1-darwin-x64/node_sqlite3.node --module_name=node_sqlite3 --module_path=/Users/.../node_modules/sqlite3/lib/binding/electron-v8.1-darwin-x64 --napi_version=5 --node_abi_napi=napi --napi_build_version=0 --node_napi_label=electron-v8.1' (1)
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! sqlite3@4.2.0 install: `node-pre-gyp install --fallback-to-build`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the sqlite3@4.2.0 install script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/.../.npm/_logs/2020-07-14T15_11_04_772Z-debug.log

Any idea?

@aguynamedben
Copy link
Author

Hmm, I don't see anything obvious, but we've learned to be very careful with our versions.

Compare SQLCipher version with node-sqlite3 version very carefully. Maybe you need to try this combo:

  • sqlcipher 4.4.0, which is built on top of SQLite 3.31.0
  • node-sqlite3 4.3.0, which is built on top of SQLite 3.31.0

I'm not sure if other combinations will work, but we're very careful to use a version of node-sqlite3 that assume the same version of SQLite that our current version of sqlcipher uses. Does that make sense? This stuff is really tedious and difficult to get right... let me know if you figure it out, I'd like to learn either way.

@ajmasia
Copy link

ajmasia commented Jul 14, 2020

Hmm, I don't see anything obvious, but we've learned to be very careful with our versions.

Compare SQLCipher version with node-sqlite3 version very carefully. Maybe you need to try this combo:

  • sqlcipher 4.4.0, which is built on top of SQLite 3.31.0
  • node-sqlite3 4.3.0, which is built on top of SQLite 3.31.0

I'm not sure if other combinations will work, but we're very careful to use a version of node-sqlite3 that assume the same version of SQLite that our current version of sqlcipher uses. Does that make sense? This stuff is really tedious and difficult to get right... let me know if you figure it out, I'd like to learn either way.

Yes, I know, currently, SQLCipher support SQLite 3.31.0 which in turn corresponds to Node SQLite version 4.2.0 this is compatible with Electron v8, v8.1.x & v8.2.x .

I am currently using version 6.1.0 of electron along with @journeyapps/node-sqlcipher, but I don't want to depend on any third-party dependencies and I would like to be able to use electron 8 for security reasons.

Any idea of how to do it? Thanks! 😄

@aguynamedben
Copy link
Author

For what it's worth, node-sqlite3 4.2.0 says it's on SQLite 3.31.1, not SQLite 3.31.0 as SQLCipher 4.4.0 is built on. I'm not sure if that matters, but wanted to point that out.

I will say from experience of using this combination of libraries for 2 years, it's hard to have the bleeding edge version of each of them all at the same time. I'd recommend getting "any working build" first, then playing with versions. Here is the versions we're using... why don't you try to get your build working with these versions and these exact commands, then upgrade the pieces once you have a working build based on these versions?

Here's our rebuild command from package.json

    "postinstall": "yarn clean-flowdefs && yarn flow-typed && yarn build-dll && yarn rebuild",
    "rebuild": "cp sqlcipher/custom-binding.gyp node_modules/sqlite3/binding.gyp && electron-rebuild --force",

custom-binding.gyp

# WORKS

{
  "includes": [ "deps/common-sqlite.gypi" ],
  "conditions": [
    ["OS != 'win'", { "variables": { "sqlcipher_path": "<!(echo $SQLCIPHER_PATH)" }}],
    ["OS == 'win'", { "variables": { "sqlcipher_path": "<!(echo %SQLCIPHER_PATH%)" }}]
  ],
  "targets": [
    {
      "target_name": "<(module_name)",
      "include_dirs": [
        "<!(node -e \"require('nan')\")",
        "<(sqlcipher_path)"
      ],
      "conditions": [
        [ "OS == 'win'", { "libraries": ["-l<(sqlcipher_path)/sqlite3",] }],
        [ "OS != 'win'", { "libraries": ["<(sqlcipher_path)/.libs/libsqlcipher.a"] }]
      ],
      "sources": [
        "src/backup.cc",
        "src/database.cc",
        "src/node_sqlite3.cc",
        "src/statement.cc"
      ]
    },
    {
      "target_name": "action_after_build",
      "type": "none",
      "dependencies": [ "<(module_name)" ],
      "copies": [
          {
            "files": [ "<(PRODUCT_DIR)/<(module_name).node" ],
            "destination": "<(module_path)"
          }
      ],
      "conditions": [
        ["OS == 'win'", {
          "copies": [
              {
                "files": [
                  "<(sqlcipher_path)/sqlite3.dll",
                  "<(sqlcipher_path)/command-e-extra/libeay32.dll",
                  "<(sqlcipher_path)/command-e-extra/msvcr120.dll"
                ],
                "destination": "<(module_path)"
              }
          ]
        }]
      ]
    }
  ]
}

SQLCIPHER_PATH in my .zshrc

  # Command E development
  export SQLCIPHER_PATH=$HOME/code/sqlcipher

Current Instructions for installing SQLCipher

SQLCipher

  1. Clone sqlcipher.
cd ~/code
git clone git@github.com:sqlcipher/sqlcipher.git
cd sqlcipher
git checkout -b v4.3.0 tags/v4.3.0
  1. Configure the build.
make clean
./configure --enable-tempstore=yes --enable-load-extension --disable-tcl --with-crypto-lib=commoncrypto\
  CFLAGS="-DSQLITE_HAS_CODEC -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_FTS3_PARENTHESIS -DSQLITE_ENABLE_FTS5"\
  LDFLAGS="-framework Security -framework Foundation"

(was -framework CoreServices instead of -framework Foundation at one point)

  1. Run the build.
make

This will produce the file ./.lib/libsqlcipher.a which will be referenced when
we run yarn rebuild. If you want to clean up the built files, run make clean.

  1. Set sqlcipher path environment variable

In order complile the sqlite node module as described below, node-gyp needs to
be made aware of the location of our sqlcipher static library files. The gyp
binding file handles this by using the $SQLCIPHER_PATH environment variable.

Export the environment variable to point to sqlcipher:

export SQLCIPHER_PATH=/Users/ben/code/sqlcipher

Other notes on compiling SQLCipher

I've spent countless hours trying to get SQLCipher to work by statically linking
OpenSSL's libcrypto.a. Building libsqlcipher.a seems to work, and so does
npm rebuilding node-sqlite3, but when Command E is deployed everything seems
to work, but the resulting database is just NOT encrypted. It seems there may be
some problem or core thing I'm missing with how statically linking OpenSSL to
SQLCipher works. ¯_(ツ)_/¯

  1. Rebuild node-sqlite3 pointing to libsqlcipher.a

This is with the logic in yarn rebuild and yarn rebuild-sqlite.

echo "Copying custom binding.gyp (node-sqlite3)"
cp sqlcipher/custom-binding.gyp node_modules/sqlite3/binding.gyp

echo "Rebuilding node-sqlite3 bindings with statically linked SQLCipher (libsqlcipher.a)"
npm rebuild sqlite3 --build-from-source --static_linking_path=/Users/ben/code/sqlcipher/.libs/libsqlcipher.a --runtime=electron --target=4.1.4 --dist-url=https://atom.io/download/electron

libsqlcipher.a is pulled into the node-sqlite3 bindings and a new binary file is
placed at
./node_modules/sqlite3/lib/binding/electron-v3.0-darwin-x64/node_sqlite3.node.
This binary has SQLCipher bundled in it, which in-turn dynamically links Common
Crypto. Running yarn package from here will build the app with the SQLCipher
features enabled.

Debugging commands

otool -L /Users/ben/code/sqlcipher/.libs/libsqlcipher.dylib
strings .libs/libsqlcipher.a | grep -i RAND_add
strings .libs/libsqlcipher.a | grep -i RAND_add

What goes wrong

  • Beware of accidental dynamic includes or configuration issues in binding.gyp.

@ajmasia
Copy link

ajmasia commented Jul 14, 2020

Hi @aguynamedben,

Thank you very much for your extensive help. I will try these tips and inform you of how everything has gone 😄

Thank you very much again!

@ajmasia
Copy link

ajmasia commented Jul 15, 2020

Thank you very much @aguynamedben, I have tried the information you have given me and it works well. Now I have to see how to implement this in my CI flow 😄.

Thanks again. I will share with you my progress!

@SrishtiSingh1902
Copy link

Hey aguynamedben, I need to link them in windows can you please help me with the same.

@aguynamedben
Copy link
Author

@ajmasia Nice! We don't have CI working with this, I'd very much appreciate if you can paste stuff that gets it working in a CI system.

@SrishtiSingh1902 We do have it working on Windows, it's something like this (below). I haven't done these steps myself, but am pasting some of the notes from one of our engineers...


# Using `c:\code` for our work
mkdir /c/code && cd /c/code
# clone the repos we need
git clone https://github.com/sqlcipher/sqlcipher.git

Add the following System Variables

  • SQLCIPHER_PATH: c:\code\sqlcipher
  • NODE_OPTIONS: --max_old_space_size=4096
# powershell as Admin
[Environment]::SetEnvironmentVariable("SQLCIPHER_PATH", "c:\code\sqlcipher", "Machine")
[Environment]::SetEnvironmentVariable("NODE_OPTIONS", "--max_old_space_size=4096", "Machine")

Log off and log back on to make sure that takes.

Setup SQLCipher

Edit the Makefile.msc or copy this one and rename to the proper directory

# terminal
code /c/code/sqlcipher/Makefile.msc

Search for DSQLITE_TEMP_STORE

replace

TCC = $(TCC) -DSQLITE_TEMP_STORE=1
RCC = $(RCC) -DSQLITE_TEMP_STORE=1

with

# UPDATED LINES
TCC = $(TCC) -DSQLITE_TEMP_STORE=2
RCC = $(RCC) -DSQLITE_TEMP_STORE=2

# ADDED LINES
TCC = $(TCC) -DSQLITE_HAS_CODEC
RCC = $(RCC) -DSQLITE_HAS_CODEC

TCC = $(TCC) -IC:\OpenSSL-Win64\include
RCC = $(RCC) -IC:\OpenSSL-Win64\include

LTLIBPATHS = $(LTLIBPATHS) /LIBPATH:C:\OpenSSL-Win64\lib /LIBPATH:C:\OpenSSL-Win64\lib\VC
# This list of file names are all the files contained in `c:\openssl\lib` folder
# Yours may be different
LTLIBS = $(LTLIBS) 4758cca.lib atalla.lib chil.lib gmp.lib libeay32.lib padlock.lib sureware.lib aep.lib capi.lib cswift.lib gost.lib nuron.lib ssleay32.lib ubsec.lib

Save your changes

  • Build SQLite with SQLCipher

Open Visual Studio 2017\x64 Native Tools Command Prompt from the application menu

# msvs 2017\x64
cd c:\code\sqlcipher
nmake /f Makefile.msc

Verify sqlcipher built

# terminal
cd /c/code/sqlcipher
git status
  • Verify sqlite3.exe, sqlite3.dll and a bunch of other untracked files now exist.
  • Copy C:\OpenSSL-Win64\libeay32.dll msvcr120.dll to C:\code\sqlcipher\command-e-extra with
mkdir /c/code/sqlcipher/command-e-extra
cp /c/OpenSSL-Win64/libeay32.dll /c/code/sqlcipher/command-e-extra/
cp /c/Windows/system32/msvcr120.dll /c/code/sqlcipher/command-e-extra/

Note that "command-e-extra" aligns with the .gyp file listed at the top of this gist. So for Windows, we point node-gyp to this command-e-extra folder. You can name it something other than Command E, that's our name :) but be sure to update your .gyp file accordingly.

Setup Tesla

# terminal
cd /c/code/tesla
yarn

Yarn should:

  • Install all dependencies

  • Setup the DLL

  • Run electron-rebuild on slqite3. This is where the magic happens. It uses the sqlcipher/custom-binding.gyp to build sqlite with our custom version.

  • Verify it worked by To check it out, go to c:\code\tesla\node_modules\sqlite3\lib\binding\ you should see:

# terminal
ls -lR /c/code/tesla/node_modules/sqlite3/lib/binding/

# the important stuff:
#
# /c/code/tesla/node_modules/sqlite3/lib/binding/electron-v5.0-win32-x64:
#
# -rwxr-xr-x 1 <user> 197121  756224 Jan 17 15:20 node_sqlite3.node*
# -rwxr-xr-x 1 <user> 197121 2064896 Jan 17 09:21 sqlite3.dll*

Add extra DLLs

This is a work in progress

For most users, Command E will work fine but some may not have the right dlls installed for NodeRT
Add a /c/code/extra-files/win-dlls folder and copy vccorlib140.dll from C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Redist\MSVC\14.16.27012\x64\Microsoft.VC141.CRT

@ajmasia
Copy link

ajmasia commented Jul 24, 2020

Sure @aguynamedben 😄

@SrishtiSingh1902
Copy link

Thanks @aguynamedben it worked :)

@aguynamedben
Copy link
Author

Rad!!!!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment