diff --git a/CMakeLists.txt b/CMakeLists.txt index 898f71f7..b0affe48 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -225,7 +225,7 @@ endif (SQLITE_ENABLE_ASSERT_HANDLER) option(SQLITE_HAS_CODEC "Enable database encryption API. Not available in the public release of SQLite." OFF) if (SQLITE_HAS_CODEC) - # Enable database encryption API. Requires implementations of sqlite3_key & sqlite3_key_v2. + # Enable database encryption API. Requires implementations of sqlite3_key & sqlite3_rekey. # Eg. SQLCipher (libsqlcipher-dev) is an SQLite extension that provides 256 bit AES encryption of database files. target_compile_definitions(SQLiteCpp PUBLIC SQLITE_HAS_CODEC) endif (SQLITE_HAS_CODEC) @@ -271,63 +271,109 @@ if (SQLITECPP_USE_GCOV) set_target_properties(SQLiteCpp PROPERTIES COMPILE_FLAGS "-fkeep-inline-functions -fkeep-static-functions") endif () -## Build provided copy of SQLite3 C library ## +# Try to handle the non-standard SQLite3 implementations first + +option(SQLITECPP_USE_SQLCIPHER "Use SQLCipher as SQLite3 library with encyption support." OFF) +if (SQLITECPP_USE_SQLCIPHER AND TARGET SQLite::SQLite3) + message(FATAL_ERROR "SQLite::SQLite3 target has already been defined, cannot use SQLCipher.") +elseif (SQLITECPP_USE_SQLCIPHER) + # Make PkgConfig optional since Windows doesn't usually have it installed. + find_package(PkgConfig QUIET) + if(PKG_CONFIG_FOUND) + # IMPORTED_TARGET was added in 3.6.3 + if(CMAKE_VERSION VERSION_LESS 3.6.3) + pkg_check_modules(SQLCipher REQUIRED sqlcipher) + # Since we can't use IMPORTED_TARGET on this older Cmake version, manually link libs & includes + message(STATUS "Link to SQLCipher library") + add_library(SQLite::SQLite3 ALIAS ${SQLCipher_LIBRARIES}) + target_link_libraries(SQLiteCpp PUBLIC ${SQLCipher_LIBRARIES}) + target_include_directories(SQLiteCpp PUBLIC ${SQLCipher_INCLUDE_DIRS}) + else() + pkg_check_modules(SQLCipher REQUIRED IMPORTED_TARGET sqlcipher) + message(STATUS "Link to SQLCipher library") + add_library(SQLite::SQLite3 ALIAS PkgConfig::SQLCipher) + target_link_libraries(SQLiteCpp PUBLIC PkgConfig::SQLCipher) + endif() + else() + # Since we aren't using pkgconf here, find it manually + find_library(SQLCipher_LIBRARY "sqlcipher") + find_path(SQLCipher_INCLUDE_DIR "sqlcipher/sqlite3.h" + PATH_SUFFIXES + "include" + "includes" + ) + # Hides it from the GUI + mark_as_advanced(SQLCipher_LIBRARY SQLCipher_INCLUDE_DIR) + if(NOT SQLCipher_INCLUDE_DIR) + message(FATAL_ERROR "${PROJECT_NAME} requires the \"\" header to use the codec functionality but it wasn't found.") + elseif(NOT SQLCipher_LIBRARY) + message(FATAL_ERROR "${PROJECT_NAME} requires the SQLCipher library to use the codec functionality but it wasn't found.") + endif() + message(STATUS "Link to SQLCipher library") + add_library(SQLite::SQLite3 ALIAS ${SQLCipher_LIBRARY}) + target_link_libraries(SQLiteCpp PUBLIC ${SQLCipher_LIBRARY}) + target_include_directories(SQLiteCpp PUBLIC "${SQLCipher_INCLUDE_DIR}/sqlcipher") + endif() + # Try to extract the SQLCipher version + file(STRINGS "${SQLCipher_INCLUDE_DIR}/sqlcipher/crypto.h" _ver_line + REGEX "^#define CIPHER_VERSION_NUMBER *\"[0-9]+\\.[0-9]+\\.[0-9]+\"" + LIMIT_COUNT 1) + string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" + SQLCipher_VERSION "${_ver_line}") + unset(_ver_line) + mark_as_advanced(SQLCipher_VERSION) + # Try to extract the SQLite3 version + file(STRINGS "${SQLCipher_INCLUDE_DIR}/sqlcipher/sqlite3.h" _ver_line + REGEX "^#define SQLITE_VERSION *\"[0-9]+\\.[0-9]+\\.[0-9]+\"" + LIMIT_COUNT 1) + string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" + SQLite3_VERSION "${_ver_line}") + unset(_ver_line) + mark_as_advanced(SQLite3_VERSION) + target_compile_definitions(SQLiteCpp PRIVATE SQLITECPP_USE_SQLCIPHER) + target_compile_definitions(SQLiteCpp PUBLIC SQLITE_HAS_CODEC) +endif () + +option(SQLITECPP_USE_SQLITE3MULTIPLECIPHERS "Use SQLite3MultipleCiphers as SQLite3 library with encyption support." OFF) +if (SQLITECPP_USE_SQLITE3MULTIPLECIPHERS AND TARGET SQLite::SQLite3) + message(FATAL_ERROR "SQLite::SQLite3 target has already been defined, cannot use SQLite3MultipleCiphers.") +elseif (SQLITECPP_USE_SQLITE3MULTIPLECIPHERS) + find_package (SQLite3MultipleCiphers REQUIRED) + message(STATUS "Link to SQLite3MultipleCiphers library") + target_link_libraries(SQLiteCpp PUBLIC SQLite::SQLite3MultipleCiphers) + target_compile_definitions(SQLiteCpp PRIVATE SQLITECPP_USE_SQLITE3MULTIPLECIPHERS) + target_compile_definitions(SQLiteCpp PUBLIC SQLITE_HAS_CODEC) +endif () option(SQLITECPP_INTERNAL_SQLITE "Add the internal SQLite3 source to the project." ON) -if (SQLITECPP_INTERNAL_SQLITE) +if (SQLITECPP_INTERNAL_SQLITE AND TARGET SQLite::SQLite3) + message(FATAL_ERROR "SQLite::SQLite3 target has already been defined, cannot use internal SQLite3.") +elseif (SQLITECPP_INTERNAL_SQLITE) + # Build the provided copy of the SQLite3 C library message(STATUS "Compile sqlite3 from source in subdirectory") option(SQLITE_ENABLE_RTREE "Enable RTree extension when building internal sqlite3 library." OFF) option(SQLITE_ENABLE_DBSTAT_VTAB "Enable DBSTAT read-only eponymous virtual table extension when building internal sqlite3 library." OFF) # build the SQLite3 C library (for ease of use/compatibility) versus Linux sqlite3-dev package add_subdirectory(sqlite3) target_link_libraries(SQLiteCpp PUBLIC SQLite::SQLite3) -else (SQLITECPP_INTERNAL_SQLITE) - # When using the SQLite codec, we need to link against the sqlcipher lib & include - # So this gets the lib & header, and links/includes everything - if(SQLITE_HAS_CODEC) - # Make PkgConfig optional since Windows doesn't usually have it installed. - find_package(PkgConfig QUIET) - if(PKG_CONFIG_FOUND) - # IMPORTED_TARGET was added in 3.6.3 - if(CMAKE_VERSION VERSION_LESS 3.6.3) - pkg_check_modules(sqlcipher REQUIRED sqlcipher) - # Only used in Database.cpp so PRIVATE to hide from end-user - # Since we can't use IMPORTED_TARGET on this older Cmake version, manually link libs & includes - target_link_libraries(SQLiteCpp PRIVATE ${sqlcipher_LIBRARIES}) - target_include_directories(SQLiteCpp PRIVATE ${sqlcipher_INCLUDE_DIRS}) - else() - pkg_check_modules(sqlcipher REQUIRED IMPORTED_TARGET sqlcipher) - # Only used in Database.cpp so PRIVATE to hide from end-user - target_link_libraries(SQLiteCpp PRIVATE PkgConfig::sqlcipher) - endif() - else() - # Since we aren't using pkgconf here, find it manually - find_library(sqlcipher_LIBRARY "sqlcipher") - find_path(sqlcipher_INCLUDE_DIR "sqlcipher/sqlite3.h" - PATH_SUFFIXES - "include" - "includes" - ) - # Hides it from the GUI - mark_as_advanced(sqlcipher_LIBRARY sqlcipher_INCLUDE_DIR) - if(NOT sqlcipher_INCLUDE_DIR) - message(FATAL_ERROR "${PROJECT_NAME} requires the \"\" header to use the codec functionality but it wasn't found.") - elseif(NOT sqlcipher_LIBRARY) - message(FATAL_ERROR "${PROJECT_NAME} requires the sqlcipher library to use the codec functionality but it wasn't found.") - endif() - # Only used in Database.cpp so PRIVATE to hide from end-user - target_include_directories(SQLiteCpp PRIVATE "${sqlcipher_INCLUDE_DIR}/sqlcipher") - target_link_libraries(SQLiteCpp PRIVATE ${sqlcipher_LIBRARY}) - endif() - else() - find_package (SQLite3 REQUIRED) - message(STATUS "Link to sqlite3 system library") - target_link_libraries(SQLiteCpp PUBLIC SQLite::SQLite3) - if(SQLite3_VERSION VERSION_LESS "3.19") - set_target_properties(SQLiteCpp PROPERTIES COMPILE_FLAGS "-DSQLITECPP_HAS_MEM_STRUCT") - endif() - endif() -endif (SQLITECPP_INTERNAL_SQLITE) + target_compile_definitions(SQLiteCpp PRIVATE SQLITECPP_INTERNAL_SQLITE) + #target_compile_definitions(SQLiteCpp PUBLIC SQLITE_HAS_CODEC) +endif () + +# Use the system library as fallback solution +if (NOT TARGET SQLite::SQLite3) + find_package (SQLite3 REQUIRED) + message(STATUS "Link to sqlite3 system library") + target_link_libraries(SQLiteCpp PUBLIC SQLite::SQLite3) +else (NOT TARGET SQLite::SQLite3) + message(STATUS "SQLite::SQLite3 target has already been defined, using it.") + target_link_libraries(SQLiteCpp PUBLIC SQLite::SQLite3) +endif (NOT TARGET SQLite::SQLite3) + +# Check the version of the found SQLite3 implementation +if(SQLite3_VERSION AND SQLite3_VERSION VERSION_LESS "3.19") + set_target_properties(SQLiteCpp PROPERTIES COMPILE_FLAGS "-DSQLITECPP_HAS_MEM_STRUCT") +endif() ## disable the optional support for std::filesystem (C++17) option(SQLITECPP_DISABLE_STD_FILESYSTEM "Disable the use of std::filesystem in SQLiteCpp." OFF) diff --git a/cmake/FindSQLite3MultipleCiphers.cmake b/cmake/FindSQLite3MultipleCiphers.cmake new file mode 100644 index 00000000..76ae5196 --- /dev/null +++ b/cmake/FindSQLite3MultipleCiphers.cmake @@ -0,0 +1,187 @@ +# Based on the FindSQLite3.cmake from the CMake project file to add support for +# SQLite3MultipleCiphers (https://github.com/utelle/SQLite3MultipleCiphers) +# to the SQLiteCpp (https://github.com/SRombauts/SQLiteCpp) project. +# +# Created in 2022 Jorrit Wronski (https://github.com/jowr) +# +# Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt +# or copy at http://opensource.org/licenses/MIT) + +#[=======================================================================[.rst: +FindSQLite3MultipleCiphers +-------------------------- + +Find the SQLite3MultipleCiphers library that implements SQLite3 with encryption. + +IMPORTED targets +^^^^^^^^^^^^^^^^ + +This module defines the following `IMPORTED` targets: + +``SQLite::SQLite3MultipleCiphers`` +``SQLite::SQLite3`` + +Result variables +^^^^^^^^^^^^^^^^ + +This module will set the following variables if found: + +``SQLite3MultipleCiphers_INCLUDE_DIRS`` + where to find sqlite3mc.h, etc. +``SQLite3MultipleCiphers_LIBRARIES`` + the libraries to link against to use SQLite3MultipleCiphers. +``SQLite3MultipleCiphers_VERSION`` + SQLite3 version of the SQLite3MultipleCiphers library found +``SQLite3MultipleCiphers_FOUND`` + TRUE if found + +It also sets alias variables for SQLite3 for campatibility reasons: + +``SQLite3_INCLUDE_DIRS`` + where to find sqlite3.h, etc. +``SQLite3_LIBRARIES`` + the libraries to link against to use SQLite3. +``SQLite3_VERSION`` + version of the SQLite3 library found +``SQLite3_FOUND`` + TRUE if found +``SQLite::SQLite3`` + the ordinary SQLite3 target for linking + +#]=======================================================================] + +find_path(SQLite3MultipleCiphers_INCLUDE_DIR NAMES sqlite3mc.h + HINTS + ${SQLITE3MULTIPLECIPHERS_DIR} + ENV SQLITE3MULTIPLECIPHERS_DIR + PATH_SUFFIXES src +) +mark_as_advanced(SQLite3MultipleCiphers_INCLUDE_DIR) + +#if("amd64" IN_LIST CMAKE_CPU_ARCHITECTURES) # I am targeting 64-bit x86. +if(CMAKE_SIZEOF_VOID_P MATCHES "8") + set(SQLite3MultipleCiphers_LIB_SUFFIX "_x64") +else() + set(SQLite3MultipleCiphers_LIB_SUFFIX "") +endif() + +find_library(SQLite3MultipleCiphers_LIBRARY_RELEASE sqlite3mc${SQLite3MultipleCiphers_LIB_SUFFIX} + HINTS + ${SQLITE3MULTIPLECIPHERS_DIR} + ENV SQLITE3MULTIPLECIPHERS_DIR + PATH_SUFFIXES + bin/vc13/lib/release + bin/vc14/lib/release + bin/vc15/lib/release + bin/vc16/lib/release + bin/vc17/lib/release + bin/gcc/lib/release + bin/clang/lib/release + bin/lib/release + REQUIRED +) +mark_as_advanced(SQLite3MultipleCiphers_LIBRARY_RELEASE) + +find_library(SQLite3MultipleCiphers_LIBRARY_DEBUG sqlite3mc${SQLite3MultipleCiphers_LIB_SUFFIX} + HINTS + ${SQLITE3MULTIPLECIPHERS_DIR} + ENV SQLITE3MULTIPLECIPHERS_DIR + PATH_SUFFIXES + bin/vc13/lib/debug + bin/vc14/lib/debug + bin/vc15/lib/debug + bin/vc16/lib/debug + bin/vc17/lib/debug + bin/gcc/lib/debug + bin/clang/lib/debug + bin/lib/debug +) +mark_as_advanced(SQLite3MultipleCiphers_LIBRARY_DEBUG) + +find_library(SQLite3MultipleCiphers_LIBRARY_MINSIZEREL sqlite3mc${SQLite3MultipleCiphers_LIB_SUFFIX} + HINTS + ${SQLITE3MULTIPLECIPHERS_DIR} + ENV SQLITE3MULTIPLECIPHERS_DIR + PATH_SUFFIXES + bin/vc13/lib/minsizerel + bin/vc14/lib/minsizerel + bin/vc15/lib/minsizerel + bin/vc16/lib/minsizerel + bin/vc17/lib/minsizerel + bin/gcc/lib/minsizerel + bin/clang/lib/minsizerel + bin/lib/minsizerel +) +mark_as_advanced(SQLite3MultipleCiphers_LIBRARY_MINSIZEREL) + +find_library(SQLite3MultipleCiphers_LIBRARY_RELWITHDEBINFO sqlite3mc${SQLite3MultipleCiphers_LIB_SUFFIX} + HINTS + ${SQLITE3MULTIPLECIPHERS_DIR} + ENV SQLITE3MULTIPLECIPHERS_DIR + PATH_SUFFIXES + bin/vc13/lib/relwithdebinfo + bin/vc14/lib/relwithdebinfo + bin/vc15/lib/relwithdebinfo + bin/vc16/lib/relwithdebinfo + bin/vc17/lib/relwithdebinfo + bin/gcc/lib/relwithdebinfo + bin/clang/lib/relwithdebinfo + bin/lib/relwithdebinfo +) +mark_as_advanced(SQLite3MultipleCiphers_LIBRARY_RELWITHDEBINFO) + +# Extract version information from the header file +if(SQLite3MultipleCiphers_INCLUDE_DIR) + file(STRINGS ${SQLite3MultipleCiphers_INCLUDE_DIR}/sqlite3mc_version.h _ver_line + REGEX "^#define SQLITE3MC_VERSION_STRING *\"[0-9]+\\.[0-9]+\\.[0-9]+\"" + LIMIT_COUNT 1) + string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" + SQLite3MultipleCiphers_VERSION "${_ver_line}") + unset(_ver_line) + mark_as_advanced(SQLite3MultipleCiphers_VERSION) +endif() + +#include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs.cmake) +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(SQLite3MultipleCiphers + REQUIRED_VARS SQLite3MultipleCiphers_INCLUDE_DIR SQLite3MultipleCiphers_LIBRARY_RELEASE SQLite3MultipleCiphers_LIBRARY_DEBUG + VERSION_VAR SQLite3MultipleCiphers_VERSION) + +# Create the imported target +if(SQLite3MultipleCiphers_FOUND) + set(SQLite3MultipleCiphers_INCLUDE_DIRS ${SQLite3MultipleCiphers_INCLUDE_DIR}) + set(SQLite3MultipleCiphers_LIBRARIES + debug ${SQLite3MultipleCiphers_LIBRARY_DEBUG} + optimized ${SQLite3MultipleCiphers_LIBRARY_RELEASE}) + if(NOT TARGET SQLite::SQLite3MultipleCiphers) + add_library(SQLite::SQLite3MultipleCiphers UNKNOWN IMPORTED) + set_target_properties(SQLite::SQLite3MultipleCiphers PROPERTIES + IMPORTED_LOCATION_DEBUG "${SQLite3MultipleCiphers_LIBRARY_DEBUG}" + IMPORTED_LOCATION_RELEASE "${SQLite3MultipleCiphers_LIBRARY_RELEASE}" + IMPORTED_LOCATION_MINSIZEREL "${SQLite3MultipleCiphers_LIBRARY_MINSIZEREL}" + IMPORTED_LOCATION_RELWITHDEBINFO "${SQLite3MultipleCiphers_LIBRARY_RELWITHDEBINFO}" + INTERFACE_INCLUDE_DIRECTORIES "${SQLite3MultipleCiphers_INCLUDE_DIR}") + endif() +endif() + +# define the alias variables +if(NOT SQLite3_INCLUDE_DIRS AND NOT SQLite3_LIBRARIES AND NOT SQLite3_VERSION AND NOT SQLite3_FOUND AND NOT TARGET SQLite::SQLite3) + set(SQLite3_INCLUDE_DIRS ${SQLite3MultipleCiphers_INCLUDE_DIRS}) + mark_as_advanced(SQLite3_INCLUDE_DIRS) + set(SQLite3_LIBRARIES ${SQLite3MultipleCiphers_LIBRARIES}) + mark_as_advanced(SQLite3_LIBRARIES) + #set(SQLite3_VERSION ${SQLite3MultipleCiphers_VERSION}) + #mark_as_advanced(SQLite3_VERSION) + file(STRINGS ${SQLite3MultipleCiphers_INCLUDE_DIR}/sqlite3.h _ver_line + REGEX "^#define SQLITE_VERSION *\"[0-9]+\\.[0-9]+\\.[0-9]+\"" + LIMIT_COUNT 1) + string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" + SQLite3_VERSION "${_ver_line}") + unset(_ver_line) + mark_as_advanced(SQLite3_VERSION) + set(SQLite3_FOUND ${SQLite3MultipleCiphers_FOUND}) + mark_as_advanced(SQLite3_FOUND) + add_library(SQLite::SQLite3 ALIAS SQLite::SQLite3MultipleCiphers) +else() + message(FATAL_ERROR "SQLite3 has already been defined, please check you options. The current settings are very likely to produce conflicts.") +endif() diff --git a/include/SQLiteCpp/Database.h b/include/SQLiteCpp/Database.h index 4475324f..8663f5b5 100644 --- a/include/SQLiteCpp/Database.h +++ b/include/SQLiteCpp/Database.h @@ -522,6 +522,23 @@ class SQLITECPP_API Database */ void loadExtension(const char* apExtensionName, const char* apEntryPointName); + /** + * @brief Set the cipher for the current sqlite database instance. + * + * This function is needed because some encryption providers require the user + * to select the cipher that they wish to use. This should happen immediately + * after opening an encrypted database: + * Open encrypted database + * -> call db.cipher("aes256cbc") + * -> call db.key("secret") + * -> database ready + * + * @param[in] aCipher Cipher to decode/encode the database + * + * @throw SQLite::Exception in case of error + */ + void cipher(const std::string& aCipher) const; + /** * @brief Set the key for the current sqlite database instance. * diff --git a/src/Database.cpp b/src/Database.cpp index f0d8d282..97a205fa 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -23,6 +23,11 @@ #define SQLITE_DETERMINISTIC 0x800 #endif // SQLITE_DETERMINISTIC +#if defined(SQLITECPP_USE_SQLITE3MULTIPLECIPHERS) +#include +#endif + + namespace SQLite { @@ -223,6 +228,38 @@ void Database::loadExtension(const char* apExtensionName, const char *apEntryPoi #endif } +// Set the cipher for the current sqlite database instance. +void Database::cipher(const std::string& aCipher) const { + int cipherLen = static_cast(aCipher.length()); +#ifdef SQLITECPP_USE_SQLCIPHER + if (cipherLen > 0) + { + throw SQLite::Exception("SQLiteCpp has been compiled with SQLCipher support, but the functionality has not been implemented, yet."); + } +#else +#ifdef SQLITECPP_USE_SQLITE3MULTIPLECIPHERS + if (cipherLen > 0) + { + int idx = sqlite3mc_cipher_index(aCipher.c_str()); + if (idx < 0) + { + throw SQLite::Exception("Could not find the cipher named \"" + aCipher + "\"."); + } + int res = sqlite3mc_config(getHandle(), "cipher", idx); + if (idx != res) + { + throw SQLite::Exception("Could not set the cipher index to \"" + std::to_string(idx) + "\"."); + } + } +#else // SQLITECPP_USE_SQLITE3MULTIPLECIPHERS + if (cipherLen > 0) + { + throw SQLite::Exception("No cipher selection support, recompile with an encryption library enabled."); + } +#endif // SQLITECPP_USE_SQLITE3MULTIPLECIPHERS +#endif // SQLITECPP_USE_SQLCIPHER +} + // Set the key for the current sqlite database instance. void Database::key(const std::string& aKey) const {