// Copyright (c) 2013-2015 IONU Security, Inc. All rights reserved // // Encrypted DB class for off-line cache and other local database storage #include #include #include #include #include #include "eyeencrypteddb.h" #include "eyelog.h" #include "eyeutils.h" using namespace sequencelogic; namespace sequencelogic { static char IONU_EYEDB_SALT[] = "74734f616e6c554949556c6e614f7374"; } EyeEncryptedDB::EyeEncryptedDB (const std::string& filename, const unsigned char* key) :EyeDB() { _filename = ""; _DEK = NULL; _IV = NULL; _db = NULL; EVP_CIPHER_CTX_init (&_ctx); int ec = SQLITE_OK; std::string sql; char* errmsg = NULL; sqlite3_stmt* stmt = NULL; int oflags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_FULLMUTEX; int cflags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX; size_t bytes = 0; unsigned char* ciphertext = NULL; char magic[256]; if (key && sequencelogic::IsValidFilename (filename)) { unsigned char salt[17]; sequencelogic::HexToBinary (IONU_EYEDB_SALT, salt); salt[16] = '\0'; //printf ("Salt: %s\n", (char*)salt); _filename = filename; _DEK = new unsigned char[SL_AES_KEY_LEN + SL_AES_BLOCK_LEN + 4]; _IV = new unsigned char[SL_AES_BLOCK_LEN + 4]; if (sqlite3_open_v2 (filename.c_str(), &_db, oflags, NULL) != SQLITE_OK) { // Close this and create new db if (_db) sqlite3_close_v2 (_db); if (sqlite3_open_v2 (filename.c_str(), &_db, cflags, NULL) != SQLITE_OK) { delete[] _DEK; _DEK = NULL; IONUDEBUG ("EyeEncryptedDB(%s) - Create failed", filename.c_str()); } else { // Create the "Header" table and insert Version, Magic, DBK, IV sql = "CREATE TABLE Header (Parameter TEXT UNIQUE, Value TEXT);"; if (sqlite3_exec (_db, sql.c_str(), NULL, NULL, &errmsg) != SQLITE_OK) { IONUDEBUG ("EyeEncryptedDB() - CREATE TABLE Header - %s\n", errmsg); } sql = "INSERT INTO Header VALUES ('Version', '1.0');"; if (sqlite3_exec (_db, sql.c_str(), NULL, NULL, &errmsg) != SQLITE_OK) { IONUDEBUG ("EyeEncryptedDB() - INSERT INTO Header Version - %s\n", errmsg); } // Generate random values for DEK (Database Encryption Key) and IV if (sequencelogic::RandomBytes (SL_AES_KEY_LEN, _DEK)) { ciphertext = new unsigned char [SL_AES_KEY_LEN + SL_AES_BLOCK_LEN]; bytes = sequencelogic::Encryptor (&_ctx, SL_AES_KEY_LEN, _DEK, ciphertext, key, salt); sql = "INSERT INTO Header VALUES ('DEK', ?);"; ec = sqlite3_prepare_v2 (_db, sql.c_str(), (int)sql.length(), &stmt, 0); if (ec != SQLITE_OK) { IONUDEBUG ("EyeEncryptedDB() - INSERT of DEK failed, prepare: %d", ec); } ec = sqlite3_bind_blob(stmt, 1, (const void*)ciphertext, (int)bytes, SQLITE_TRANSIENT); if (ec != SQLITE_OK) { IONUDEBUG ("EyeEncryptedDB() - INSERT of DEK failed, bind: %d", ec); } ec = sqlite3_step (stmt); if (ec != SQLITE_DONE && ec != SQLITE_OK) { IONUDEBUG ("EyeEncryptedDB() - INSERT of DEK failed, step: %d", ec); } ec = sqlite3_clear_bindings(stmt); if (ec != SQLITE_OK) { IONUDEBUG ("EyeEncryptedDB() - INSERT of DEK failed, clear: %d", ec); } ec = sqlite3_finalize (stmt); if (ec != SQLITE_OK) { IONUDEBUG ("EyeEncryptedDB() - INSERT of DEK failed, finalize: %d", ec); } //sequencelogic::Base64Encode (ciphertext, bytes, b64); //printf ("dek: %s\n", b64); delete[] ciphertext; ciphertext = NULL; } else { IONUDEBUG ("EyeEncryptedDB() - DEK generation failed"); } if (sequencelogic::RandomBytes (SL_AES_BLOCK_LEN, _IV)) { sql = "INSERT INTO Header VALUES ('IV', ?);"; ec = sqlite3_prepare_v2 (_db, sql.c_str(), (int)sql.length(), &stmt, 0); if (ec != SQLITE_OK) { IONUDEBUG ("EyeEncryptedDB() - INSERT of IV failed, prepare: %d", ec); } ec = sqlite3_bind_blob(stmt, 1, (const void*)_IV, SL_AES_BLOCK_LEN, SQLITE_TRANSIENT); if (ec != SQLITE_OK) { IONUDEBUG ("EyeEncryptedDB() - INSERT of IV failed, bind: %d", ec); } ec = sqlite3_step (stmt); if (ec != SQLITE_DONE && ec != SQLITE_OK) { IONUDEBUG ("EyeEncryptedDB() - INSERT of IV failed, step: %d", ec); } ec = sqlite3_clear_bindings(stmt); if (ec != SQLITE_OK) { IONUDEBUG ("EyeEncryptedDB() - INSERT of IV failed, clear: %d", ec); } ec = sqlite3_finalize (stmt); if (ec != SQLITE_OK) { IONUDEBUG ("EyeEncryptedDB() - INSERT of IV failed, finalize: %d", ec); } } else { IONUDEBUG ("EyeEncryptedDB() - IV generation failed"); } // Encrypt the magic value and insert into Header bytes = sequencelogic::Encryptor (&_ctx, 5, (unsigned char*)"IOnU", (unsigned char*)magic, _DEK, _IV); //sequencelogic::Base64Encode ((unsigned char*)magic, bytes, b64); //printf ("Encrypted Magic, %d bytes, %s\n", (int)bytes, b64); sql = "INSERT INTO Header VALUES ('Magic', ?);"; //std::cout << sql << std::endl; ec = sqlite3_prepare_v2 (_db, sql.c_str(), (int)sql.length(), &stmt, 0); if (ec != SQLITE_OK) { IONUDEBUG ("EyeEncryptedDB() - INSERT of Magic failed, prepare: %d", ec); } ec = sqlite3_bind_blob(stmt, 1, (const void*)magic, (int)bytes, SQLITE_TRANSIENT); if (ec != SQLITE_OK) { IONUDEBUG ("EyeEncryptedDB() - INSERT of Magic failed, bind: %d", ec); } ec = sqlite3_step (stmt); if (ec != SQLITE_DONE && ec != SQLITE_OK) { IONUDEBUG ("EyeEncryptedDB() - INSERT of Magic failed, step: %d", ec); } ec = sqlite3_clear_bindings(stmt); if (ec != SQLITE_OK) { IONUDEBUG ("EyeEncryptedDB() - INSERT of Magic failed, clear: %d", ec); } ec = sqlite3_finalize (stmt); if (ec != SQLITE_OK) { IONUDEBUG ("EyeEncryptedDB() - INSERT of Magic failed, finalize: %d", ec); } } //sequencelogic::Base64Encode (_DEK, SL_AES_KEY_LEN, b64); //printf ("DEK: %s\n", b64); //sequencelogic::Base64Encode (_IV, SL_AES_BLOCK_LEN, b64); //printf ("IV: %s\n", b64); } // Opened existing db else { sql = "SELECT * FROM Header WHERE Parameter == 'DEK';"; ec = sqlite3_prepare_v2 (_db, sql.c_str(), (int)sql.length(), &stmt, 0); if (ec != SQLITE_OK) { IONUDEBUG ("EyeEncryptedDB() - SELECT of DEK failed, prepare: %d", ec); } ec = sqlite3_step (stmt); if (ec != SQLITE_ROW) { IONUDEBUG ("EyeEncryptedDB() - SELECT of DEK failed, step: %d", ec); } bytes = sqlite3_column_bytes (stmt, 1); ciphertext = (unsigned char*) sqlite3_column_blob (stmt, 1); if (ciphertext) { bytes = Decryptor (&_ctx, bytes, ciphertext, _DEK, key, salt); if (bytes != SL_AES_KEY_LEN) { IONUDEBUG ("EyeEncryptedDB() - get of DEK failed, wrong number of bytes: %d", (int)bytes); } } else { IONUDEBUG ("EyeEncryptedDB() - get of DEK failed"); } ec = sqlite3_finalize (stmt); if (ec != SQLITE_OK) { IONUDEBUG ("EyeEncryptedDB() - SELECT of DEK failed, finalize: %d", ec); } sql = "SELECT * FROM Header WHERE Parameter == 'IV';"; ec = sqlite3_prepare_v2 (_db, sql.c_str(), (int)sql.length(), &stmt, 0); if (ec != SQLITE_OK) { IONUDEBUG ("EyeEncryptedDB() - SELECT of IV failed: %d", ec); } ec = sqlite3_step (stmt); if (ec != SQLITE_ROW) { IONUDEBUG ("EyeEncryptedDB() - SELECT of IV failed, step: %d", ec); } unsigned char* iv = (unsigned char*) sqlite3_column_blob (stmt, 1); bytes = sqlite3_column_bytes (stmt, 1); if (iv && bytes == SL_AES_BLOCK_LEN) { sequencelogic::MemCpy (_IV, iv, SL_AES_BLOCK_LEN); } else { IONUDEBUG ("EyeEncryptedDB() - get of IV failed %d bytes", bytes); } ec = sqlite3_finalize (stmt); if (ec != SQLITE_OK) { IONUDEBUG ("EyeEncryptedDB() - SELECT of IV failed, finalize: %d", ec); } sql = "SELECT * FROM Header WHERE Parameter == 'Magic';"; ec = sqlite3_prepare_v2 (_db, sql.c_str(), (int)sql.length(), &stmt, 0); if (ec != SQLITE_OK) { IONUDEBUG ("EyeEncryptedDB() - SELECT of Magic failed, prepare: %d", ec); } ec = sqlite3_step (stmt); if (ec != SQLITE_ROW) { IONUDEBUG ("EyeEncryptedDB() - SELECT of Magic failed, step: %d", ec); } bytes = sqlite3_column_bytes (stmt, 1); ciphertext = (unsigned char*) sqlite3_column_blob (stmt, 1); bool goodMagic = false; if (ciphertext) { bytes = Decryptor (&_ctx, bytes, ciphertext, (unsigned char*)magic, _DEK, _IV); if (bytes == 5 && sequencelogic::StrCmp (magic, "IOnU") == 0) goodMagic = true; } if (!goodMagic) { IONUDEBUG ("EyeEncryptedDB() - bad magic"); if (_DEK) { delete[] _DEK; _DEK = NULL; } } ec = sqlite3_finalize (stmt); if (ec != SQLITE_OK) { IONUDEBUG ("EyeEncryptedDB() - SELECT of Magic failed, finalize: %d", ec); } //sequencelogic::Base64Encode (_DEK, SL_AES_KEY_LEN, b64); //printf ("DEK: %s\n", b64); //sequencelogic::Base64Encode (_IV, SL_AES_BLOCK_LEN, b64); //printf ("IV: %s\n", b64); } } } EyeEncryptedDB::~EyeEncryptedDB () { if (_DEK) { sequencelogic::MemSet (_DEK, 0, SL_AES_KEY_LEN); delete[] _DEK; _DEK = NULL; } if (_IV) { sequencelogic::MemSet (_IV, 0, SL_AES_BLOCK_LEN); delete[] _IV; _IV = NULL; } if (_db) { sqlite3_close_v2 (_db); _db = NULL; } EVP_CIPHER_CTX_cleanup (&_ctx); } bool EyeEncryptedDB::Put (const std::string& collection, const std::string& key, const std::string& value) { if (!_DEK) { IONUDEBUG ("EyeEncryptedDB::Put() - DB not open"); return false; } else if (collection.size() < 1 || key.size() < 1 || value.size() < 1) { IONUDEBUG ("EyeEncryptedDB::Put() - Invalid collection, key and/or value"); return false; } else { size_t bytes = 0; size_t len = value.size(); //create table if necessary std::stringstream table; table << "CREATE TABLE IF NOT EXISTS " << collection << " (id TEXT UNIQUE, value TEXT);"; if (!executeQuery(table.str())) { IONUDEBUG ("EyeEncryptedDB::Put(%s, %s) - executeQuery failed", collection.c_str(), key.c_str()); return false; } //prepare statement std::stringstream sql; sql << "INSERT OR REPLACE INTO " << collection << " VALUES (?,?);"; statement stmt(*this, sql.str()); if (!stmt.isPrepared()) { IONUDEBUG ("EyeEncryptedDB::Put(%s, %s) - isPrepared failed", collection.c_str(), key.c_str()); return false; } //encrypt value std::unique_ptr ciphertext(new unsigned char[len + 2*SL_AES_BLOCK_LEN]); bytes = sequencelogic::Encryptor (&_ctx, len, (unsigned char*)value.data(), ciphertext.get(), _DEK, _IV); //bind arguments and insert row if (stmt.bind(1, key) && stmt.bind(2, ciphertext.get(), (int)bytes)) { int ec = stmt.step(); if (ec != SQLITE_ROW && ec != SQLITE_DONE) { IONUDEBUG ("EyeEncryptedDB::Put(%s, %s) - step failed: %d", collection.c_str(), key.c_str(), ec); } } } return true; } char* EyeEncryptedDB::Get (const std::string& collection, const std::string& key) { if (!_DEK) { IONUDEBUG ("EyeEncryptedDB::Get() - DB not open"); return NULL; } else if (collection.size() < 1 || key.size() < 1) { IONUDEBUG ("EyeEncryptedDB::Get() - Invalid collection and/or key"); return NULL; } else { unsigned char* value = NULL; size_t bytes = 0; std::stringstream sql; sql << "SELECT * FROM " << collection << " WHERE id == ?"; statement stmt(*this, sql.str()); if (!stmt.isPrepared()) { IONUDEBUG ("EyeEncryptedDB::Get(%s, %s) - prepare failed", collection.c_str(), key.c_str()); return NULL; } if (stmt.bind(1, key)) { int ec = stmt.step(); if (ec == SQLITE_ROW) { //value = sequencelogic::StrDup (sqlite3_column_text (stmt, 1)); unsigned char* ciphertext = (unsigned char*) stmt.columnBlob(1); bytes = (size_t) stmt.columnBytesInt(1); //char b64[256]; //sequencelogic::Base64Encode (ciphertext, bytes, b64); //printf ("dec: %s.%s %s\n", collection, key, b64); if (ciphertext) { value = new unsigned char[bytes+1]; bytes = Decryptor (&_ctx, bytes, ciphertext, value, _DEK, _IV); value[bytes] = '\0'; } } else if (ec == SQLITE_DONE) { IONUDEBUG ("EyeEncryptedDB::Get(%s, %s) - key not found", collection.c_str(), key.c_str()); } else { IONUDEBUG ("EyeEncryptedDB::Get(%s, %s) - step failed %d", collection.c_str(), key.c_str(), ec); } } return (char*)value; } } bool EyeEncryptedDB::PutAll (const std::string& collection, const std::map& entries, bool remove) { if (!_DEK) { IONUDEBUG ("EyeEncryptedDB::Put() - DB not open"); return false; } else if (collection.size() < 1) { IONUDEBUG ("EyeEncryptedDB::PutAll() - Invalid collection"); return false; } else { //Create the table if necessary std::stringstream table; table << "CREATE TABLE IF NOT EXISTS " << collection << " (id TEXT UNIQUE, value TEXT);"; if (!executeQuery(table.str())) { return false; } // Protect optional remove and puts in a transaction, on failure this will rollback to unchanged version { transaction transaction(*this); //remove existing rows if requested if (remove) { std::stringstream sql; sql << "DELETE FROM " << collection; if (!executeQuery(table.str())) { return false; } } //Create query to insert/replace rows std::stringstream sql; sql << "INSERT OR REPLACE INTO " << collection << " VALUES (?, ?)"; statement stmt(*this, sql.str()); if (!stmt.isPrepared()) { IONUDEBUG ("EyeEncryptedDB::PutAll(%s) - prepare failed", collection.c_str()); return false; } //iterate new key/values and update table for (std::map::const_iterator itr = entries.begin(); itr != entries.end(); ++itr) { //encrypt value size_t len = itr->second.size(); std::unique_ptr ciphertext(new unsigned char[len + 2*SL_AES_BLOCK_LEN]); size_t bytes = sequencelogic::Encryptor (&_ctx, len, (unsigned char*) itr->second.c_str(), ciphertext.get(), _DEK, _IV); //bind key/value and step if (stmt.bind(1, itr->first) && stmt.bind(2, ciphertext.get(), (int)bytes)) { //insert/update row int result = stmt.step(); if (result != SQLITE_DONE) { IONUDEBUG ("EyeEncryptedDB::PutAll(%s) - insert failed", collection.c_str()); return false; } stmt.reset(); } else { IONUDEBUG ("EyeEncryptedDB::PutAll(%s) - bind failed", collection.c_str()); return false; } } transaction.commit(); } return true; } } STRING_STRING_MAP EyeEncryptedDB::GetAll (const std::string& collection) { STRING_STRING_MAP entries; if (!_DEK) { IONUDEBUG ("EyeEncryptedDB::GetAll() - DB not open"); } else if (collection.size() < 1) { IONUDEBUG ("EyeEncryptedDB::GetAll() - Invalid collection"); } else { std::stringstream sql; sql << "SELECT * FROM " << collection; statement stmt(*this, sql.str()); if (stmt.isPrepared()) { while (stmt.step() == SQLITE_ROW) { //key std::string key = (const char*)(stmt.columnText(0)); //decrypt value from ciphertext in column unsigned char* ciphertext = (unsigned char*) stmt.columnBlob(1); size_t bytes = (size_t) stmt.columnBytesInt(1); if (ciphertext) { std::unique_ptr value(new char[bytes+1]); bytes = Decryptor (&_ctx, bytes, ciphertext, (unsigned char*)value.get(), _DEK, _IV); entries[key] = std::string(value.get(), bytes); } } } else { IONUDEBUG ("EyeEncryptedDB::GetAll() - prepare failed"); } } return entries; } bool EyeEncryptedDB::ReKey (const unsigned char* newTGIkey) { if (!_DEK) { IONUDEBUG ("EyeEncryptedDB::ReKey() - DB not open"); return false; } else if (!newTGIkey) { IONUDEBUG ("EyeEncryptedDB::ReKey() - Invalid TGI key"); return false; } if (!_DEK) { IONUDEBUG ("EyeDB::ReKey() - DB not open"); return false; } else if (!newTGIkey) { IONUDEBUG ("EyeDB::ReKey() - Invalid TGI key"); return false; } size_t bytes = 0; unsigned char salt[17]; sequencelogic::HexToBinary (IONU_EYEDB_SALT, salt); unsigned char ciphertext[256]; bytes = sequencelogic::Encryptor (&_ctx, SL_AES_KEY_LEN, _DEK, ciphertext, newTGIkey, salt); std::string sql = "REPLACE INTO Header VALUES ('DEK', ?);"; statement stmt(*this, sql); if (stmt.isPrepared() && stmt.bind(1, ciphertext, (int)bytes)) { int ec = stmt.step(); if (ec == SQLITE_DONE) return true; } return false; } bool EyeEncryptedDB::Remove (const std::string& collection) { if (!_DEK) { IONUDEBUG ("EyeEncryptedDB::Remove() - DB not open"); return false; } else if (collection.size() < 1) { IONUDEBUG ("EyeEncryptedDB::Remove() - Invalid collection"); return false; } else { std::stringstream sql; sql << "DROP TABLE IF EXISTS " << collection; statement stmt(*this, sql.str()); if (stmt.isPrepared()) { int ec = stmt.step(); if (ec != SQLITE_DONE) { IONUDEBUG ("EyeEncryptedDB::Remove() - step failed"); return false; } } else { IONUDEBUG ("EyeEncryptedDB::Remove() - prepare failed"); return false; } } return true; } bool EyeEncryptedDB::Remove (const std::string& collection, const std::string& key) { if (!_DEK) { IONUDEBUG ("EyeEncryptedDB::Remove() - DB not open"); return false; } else if (collection.size() < 1 || key.size() < 1) { IONUDEBUG ("EyeEncryptedDB::Remove() - Invalid collection and/or key"); return false; } else { std::stringstream sql; sql << "DELETE FROM " << collection << " WHERE id == ?"; statement stmt(*this, sql.str()); if (stmt.isPrepared() && stmt.bind(1, key)) { int ec = stmt.step(); if (ec != SQLITE_DONE) { IONUDEBUG ("EyeEncryptedDB::Remove() - step failed"); return false; } } else { IONUDEBUG ("EyeEncryptedDB::Remove() - prepare failed"); return false; } } return true; } void EyeEncryptedDB::Dump (const std::string& collection) { size_t bytes = 0; std::string sql; sqlite3_stmt* stmt = NULL; sql = "SELECT * FROM Header;"; int ec = sqlite3_prepare_v2 (_db, sql.c_str(), (int)sql.length(), &stmt, 0); if (ec != SQLITE_OK) { IONUDEBUG ("EyeEncryptedDB::Dump() - SELECT * FROM Header failed, prepare: %d", ec); } printf ("Header {\n"); while (sqlite3_step (stmt) == SQLITE_ROW) { const unsigned char* key = sqlite3_column_text (stmt, 0); unsigned char* val = (unsigned char*) sqlite3_column_blob (stmt, 1); bytes = (size_t) sqlite3_column_bytes (stmt, 1); if (!sequencelogic::IsStrAscii ((char*) val, bytes)) { char b64[256]; sequencelogic::Base64Encode (val, bytes, b64); printf (" %s: %s\n", key, b64); } else { printf (" %s: %s\n", key, (char*)val); } } ec = sqlite3_finalize (stmt); if (ec != SQLITE_OK) { IONUDEBUG ("EyeEncryptedDB::Dump() - SELECT * FROM Header failed, finalize: %d", ec); } printf ("}\n"); sql = "SELECT * FROM "; sql += collection; sql += ";"; ec = sqlite3_prepare_v2 (_db, sql.c_str(), (int)sql.length(), &stmt, 0); if (ec != SQLITE_OK) { IONUDEBUG ("EyeEncryptedDB::Dump() - SELECT * FROM %s failed, prepare: %d", collection.c_str(), ec); } std::cout << collection << " {" << std::endl; while (sqlite3_step (stmt) == SQLITE_ROW) { const unsigned char* key = sqlite3_column_text (stmt, 0); unsigned char* val = (unsigned char*) sqlite3_column_blob (stmt, 1); bytes = (size_t) sqlite3_column_bytes (stmt, 1); if (!sequencelogic::IsStrAscii ((char*) val, bytes)) { char b64[256]; sequencelogic::Base64Encode (val, bytes, b64); std::cout << " " << key << ": " << b64 << std::endl; } else { printf (" %s: %s\n", key, (char*)val); } } ec = sqlite3_finalize (stmt); if (ec != SQLITE_OK) { IONUDEBUG ("EyeEncryptedDB::Dump() - SELECT * FROM %s failed, finalize: %d", collection.c_str(), ec); } printf ("}\n"); }