diff --git a/README.md b/README.md index 80544f3e..4147298e 100644 --- a/README.md +++ b/README.md @@ -256,6 +256,22 @@ The helper APIs interpolate table and column names into SQL; use trusted identif --- +## Encryption + +Database encryption is supported through [SQLite Encryption Extension](https://sqlite.org/see) (SEE). + +As SEE is a paid product, you need to patch the files `react-native-nitro-sqlite/cpp/sqlite/sqlite3.c` and `react-native-nitro-sqlite/cpp/sqlite/sqlite3.h` after installation using `bun patch react-native-nitro-sqlite`. Replace them with the `sqlite.h` and `sqlite3-see-.c` files from the SEE sources. + +In addition, the [pre-processor flag](#enable-compile-time-options) `SQLITE_ENABLE_SEE` needs to be specified. + +You can then use encryption, by passing an encryption key when opening the database + +```typescript +import {open} from 'react-native-nitro-sqlite' + +const db = open({name: 'myDb.sqlite', encryptionKey: 'SUPER_SECURE_ENCRYPTION_KEY'}) +``` + # TypeORM You can use this package as a TypeORM driver. Because of Metro and Node resolution, TypeORM’s `package.json` must be exposed and the driver aliased. diff --git a/packages/react-native-nitro-sqlite/cpp/NitroSQLiteException.hpp b/packages/react-native-nitro-sqlite/cpp/NitroSQLiteException.hpp index e63f5d70..b0178993 100644 --- a/packages/react-native-nitro-sqlite/cpp/NitroSQLiteException.hpp +++ b/packages/react-native-nitro-sqlite/cpp/NitroSQLiteException.hpp @@ -10,6 +10,8 @@ const std::string NITRO_SQLITE_EXCEPTION_PREFIX = "[NativeNitroSQLiteException]" enum NitroSQLiteExceptionType { UnknownError, DatabaseCannotBeOpened, + EncryptionNotEnabled, + DatabaseCannotBeDecrypted, DatabaseNotOpen, UnableToAttachToDatabase, SqlExecutionError, @@ -20,6 +22,8 @@ enum NitroSQLiteExceptionType { inline std::unordered_map exceptionTypeStrings = { {UnknownError, "UnknownError"}, {DatabaseCannotBeOpened, "DatabaseCannotBeOpened"}, + {EncryptionNotEnabled, "EncryptionNotEnabled"}, + {DatabaseCannotBeDecrypted, "DatabaseCannotBeDecrypted"}, {DatabaseNotOpen, "DatabaseNotOpen"}, {UnableToAttachToDatabase, "UnableToAttachToDatabase"}, {SqlExecutionError, "SqlExecutionError"}, diff --git a/packages/react-native-nitro-sqlite/cpp/hybridObjects/HybridNitroSQLite.cpp b/packages/react-native-nitro-sqlite/cpp/hybridObjects/HybridNitroSQLite.cpp index 60a1d63f..752c1d00 100644 --- a/packages/react-native-nitro-sqlite/cpp/hybridObjects/HybridNitroSQLite.cpp +++ b/packages/react-native-nitro-sqlite/cpp/hybridObjects/HybridNitroSQLite.cpp @@ -65,9 +65,9 @@ const std::string getDocPath(const std::optional& location) { return tempDocPath; } -void HybridNitroSQLite::open(const std::string& dbName, const std::optional& location) { +void HybridNitroSQLite::open(const std::string& dbName, const std::optional& location, const std::optional& encryptionKey) { const auto docPath = getDocPath(location); - sqliteOpenDb(dbName, docPath); + sqliteOpenDb(dbName, docPath, encryptionKey); } void HybridNitroSQLite::close(const std::string& dbName) { diff --git a/packages/react-native-nitro-sqlite/cpp/hybridObjects/HybridNitroSQLite.hpp b/packages/react-native-nitro-sqlite/cpp/hybridObjects/HybridNitroSQLite.hpp index a5331964..1edc51ec 100644 --- a/packages/react-native-nitro-sqlite/cpp/hybridObjects/HybridNitroSQLite.hpp +++ b/packages/react-native-nitro-sqlite/cpp/hybridObjects/HybridNitroSQLite.hpp @@ -17,7 +17,7 @@ class HybridNitroSQLite : public HybridNitroSQLiteSpec { public: // Methods - void open(const std::string& dbName, const std::optional& location) override; + void open(const std::string& dbName, const std::optional& location, const std::optional& encryptionKey) override; void close(const std::string& dbName) override; diff --git a/packages/react-native-nitro-sqlite/cpp/operations.cpp b/packages/react-native-nitro-sqlite/cpp/operations.cpp index ae61dc65..4b9d1a60 100644 --- a/packages/react-native-nitro-sqlite/cpp/operations.cpp +++ b/packages/react-native-nitro-sqlite/cpp/operations.cpp @@ -47,9 +47,21 @@ void sqliteOpenDb(const std::string& dbName, const std::string& docPath) { if (exit != SQLITE_OK) { throw NitroSQLiteException(NitroSQLiteExceptionType::DatabaseCannotBeOpened, sqlite3_errmsg(db)); - } else { - dbMap[dbName] = db; } + + if (encryptionKey.has_value()) { + #ifdef SQLITE_ENABLE_SEE + exit = sqlite3_key_v2(db, "main", encryptionKey.value().c_str(), -1); + + if (exit != SQLITE_OK) { + throw NitroSQLiteException(NitroSQLiteExceptionType::DatabaseCannotBeDecrypted, sqlite3_errmsg(db)); + } + #else + throw NitroSQLiteException(NitroSQLiteExceptionType::EncryptionNotEnabled, "Enable encryption by specifying SQLITE_ENABLE_SEE pre-processor flag and replacing sqlite.h / sqlite.c."); + #endif + } + + dbMap[dbName] = db; } void sqliteCloseDb(const std::string& dbName) { diff --git a/packages/react-native-nitro-sqlite/cpp/operations.hpp b/packages/react-native-nitro-sqlite/cpp/operations.hpp index 49fce8ca..acba6d23 100644 --- a/packages/react-native-nitro-sqlite/cpp/operations.hpp +++ b/packages/react-native-nitro-sqlite/cpp/operations.hpp @@ -1,11 +1,15 @@ #pragma once +#ifdef SQLITE_ENABLE_SEE + #define SQLITE_HAS_CODEC 1 // Enable SQLite encryption support +#endif + #include "hybridObjects/HybridNitroSQLiteQueryResult.hpp" #include "types.hpp" namespace margelo::rnnitrosqlite { -void sqliteOpenDb(const std::string& dbName, const std::string& docPath); +void sqliteOpenDb(const std::string& dbName, const std::string& docPath, const std::optional& encryptionKey); void sqliteCloseDb(const std::string& dbName); diff --git a/packages/react-native-nitro-sqlite/src/operations/session.ts b/packages/react-native-nitro-sqlite/src/operations/session.ts index 391407f2..4e112bbc 100644 --- a/packages/react-native-nitro-sqlite/src/operations/session.ts +++ b/packages/react-native-nitro-sqlite/src/operations/session.ts @@ -18,7 +18,11 @@ export function open( options: NitroSQLiteConnectionOptions, ): NitroSQLiteConnection { try { - HybridNitroSQLite.open(options.name, options.location) + HybridNitroSQLite.open( + options.name, + options.location, + options.encryptionKey, + ) openDatabaseQueue(options.name) } catch (error) { throw NitroSQLiteError.fromError(error) diff --git a/packages/react-native-nitro-sqlite/src/specs/NitroSQLite.nitro.ts b/packages/react-native-nitro-sqlite/src/specs/NitroSQLite.nitro.ts index 650493db..05ab140c 100644 --- a/packages/react-native-nitro-sqlite/src/specs/NitroSQLite.nitro.ts +++ b/packages/react-native-nitro-sqlite/src/specs/NitroSQLite.nitro.ts @@ -12,7 +12,7 @@ export interface NitroSQLite ios: 'c++' android: 'c++' }> { - open(dbName: string, location?: string): void + open(dbName: string, location?: string, encryptionKey?: string): void close(dbName: string): void drop(dbName: string, location?: string): void attach( diff --git a/packages/react-native-nitro-sqlite/src/typeORM.ts b/packages/react-native-nitro-sqlite/src/typeORM.ts index 83e94bf1..207490f7 100644 --- a/packages/react-native-nitro-sqlite/src/typeORM.ts +++ b/packages/react-native-nitro-sqlite/src/typeORM.ts @@ -40,6 +40,7 @@ export const typeORMDriver = { options: { name: string location?: string + encryptionKey?: string }, ok: (db: TypeOrmNitroSQLiteConnection) => void, fail: (msg: string) => void, diff --git a/packages/react-native-nitro-sqlite/src/types.ts b/packages/react-native-nitro-sqlite/src/types.ts index 517bcca3..25297fa6 100644 --- a/packages/react-native-nitro-sqlite/src/types.ts +++ b/packages/react-native-nitro-sqlite/src/types.ts @@ -3,6 +3,7 @@ import type { NitroSQLiteQueryResult } from './specs/NitroSQLiteQueryResult.nitr export interface NitroSQLiteConnectionOptions { name: string location?: string + encryptionKey?: string } export interface NitroSQLiteConnection {