#include "../include/odhtdb/DatabaseStorage.hpp" #include "../include/odhtdb/Group.hpp" #include "../include/odhtdb/FileUtils.hpp" #include "../include/odhtdb/bin2hex.hpp" #include "../include/odhtdb/PasswordHash.hpp" #include "../include/odhtdb/Log.hpp" #include "../include/odhtdb/Database.hpp" #include #include #include #include #include #include using namespace std; // TODO: The latest user data will be used when processing old nodes. When processing a node the user state should be the same as it was at the time the node was created. // This can be done by having history of changes in user data so we can retrieve the user data state at a specific time in history. namespace odhtdb { enum StorageType : u8 { STORAGE_TYPE_CREATE, STORAGE_TYPE_APPEND }; const u64 QUARANTINE_STORAGE_TIME_MICROSECONDS = 60 * 1.0e6; const u16 STORAGE_VERSION = 5; static void sqlite_exec_checked(sqlite3 *db, const char *sql) { char *dbErrMsg = nullptr; int rc = sqlite3_exec(db, sql, nullptr, nullptr, &dbErrMsg); if(rc != SQLITE_OK) { string errMsg = "Failed to run sqlite exec, error: "; errMsg += dbErrMsg; sqlite3_free(dbErrMsg); throw DatabaseStorageException(errMsg); } } static void sqlite_prepare_checked(sqlite3 *db, const char *sql, sqlite3_stmt **stmt) { int rc = sqlite3_prepare_v2(db, sql, -1, stmt, nullptr); if(rc != SQLITE_OK) { string errMsg = "Failed to run sqlite prepare statement, error: "; errMsg += sqlite3_errmsg(db); throw DatabaseStorageException(errMsg); } } DatabaseStorage::DatabaseStorage(Database *_database, const boost::filesystem::path &storagePath) : database(_database), sqliteDb(nullptr), insertNodeStmt(nullptr), insertUserStmt(nullptr), insertGroupStmt(nullptr), insertNodeAddDataStmt(nullptr), setNodeDecryptionKeyStmt(nullptr), getNodeDecryptionKeyStmt(nullptr), selectNodeStmt(nullptr), selectNodeAddDataByNodeStmt(nullptr), selectNodeIdStatement(nullptr), selectNodeAddDataIdStatement(nullptr), insertNodeUserGroupAssocStmt(nullptr), insertNodeRawStmt(nullptr), insertNodeAddDataRawStmt(nullptr), insertNodeAddDataAdditionalStmt(nullptr), insertNodeAddUserDataStmt(nullptr), selectNodeAddDataAdditionalStmt(nullptr), selectNodeAddUserDataStmt(nullptr), setNodeAddDataDecryptedStmt(nullptr), setNodeAddDataAdditionalDataStmt(nullptr), metadataFilePath(storagePath / "metadata"), remoteNodesFilePath(storagePath / "remote_nodes") { try { init(storagePath); } catch(DatabaseStorageException &e) { cleanup(); throw; } } void DatabaseStorage::init(const boost::filesystem::path &storagePath) { boost::filesystem::create_directories(storagePath); Log::debug("sqlite version: %s", sqlite3_libversion()); int rc = sqlite3_open((storagePath / "database.sqlite3").string().c_str(), &sqliteDb); if(rc != SQLITE_OK) { string errMsg = "Failed to open database: "; errMsg += sqlite3_errmsg(sqliteDb); throw DatabaseStorageException(errMsg); } // TODO: GroupId (and maybe other fields) have to be unique, but by malicious intervention group id can be copied from one node and then used when creating another one. // This will result in error for users that are part of the other Node, meaning not all users... sqlite_exec_checked(sqliteDb, "CREATE TABLE IF NOT EXISTS Node(id INTEGER PRIMARY KEY, nodeHash BLOB UNIQUE NOT NULL, timestamp INTEGER NOT NULL, creatorPublicKey BLOB NOT NULL, adminGroupId BLOB NOT NULL);" "CREATE TABLE IF NOT EXISTS NodeUser(id INTEGER PRIMARY KEY, node BLOB NOT NULL, publicKey BLOB NOT NULL, latestActionCounter INTEGER NOT NULL, FOREIGN KEY(node) REFERENCES Node(nodeHash));" "CREATE TABLE IF NOT EXISTS NodeGroup(node BLOB NOT NULL, groupId BLOB UNIQUE NOT NULL, permissionLevel INT NOT NULL, permissionFlags INTEGER NOT NULL, FOREIGN KEY(node) REFERENCES Node(nodeHash));" "CREATE TABLE IF NOT EXISTS NodeAddData(id INTEGER PRIMARY KEY, node BLOB NOT NULL, requestHash BLOB UNIQUE NOT NULL, operation INT NOT NULL, timestamp INTEGER NOT NULL, creatorPublicKey BLOB NOT NULL, decrypted INT NOT NULL, userActionCounter INTEGER NOT NULL, FOREIGN KEY(node) REFERENCES Node(nodeHash));" "CREATE TABLE IF NOT EXISTS NodeAddDataAdditional(id INTEGER PRIMARY KEY, nodeAddDataId INTEGER NOT NULL, data BLOB NOT NULL, FOREIGN KEY(nodeAddDataId) REFERENCES NodeAddData(id));" "CREATE TABLE IF NOT EXISTS NodeAddUserData(id INTEGER PRIMARY KEY, nodeAddDataId INTEGER NOT NULL, userToAddPublicKey BLOB NOT NULL, groupId BLOB NOT NULL, FOREIGN KEY(nodeAddDataId) REFERENCES NodeAddData(id));" "CREATE TABLE IF NOT EXISTS NodeDecryptionKey(node BLOB UNIQUE NOT NULL, decryptionKey BLOB NOT NULL);" "CREATE TABLE IF NOT EXISTS NodeUserGroupAssoc(node BLOB NOT NULL, userPublicKey BLOB NOT NULL, groupId BLOB NOT NULL, FOREIGN KEY(node, userPublicKey) REFERENCES NodeUser(node, publicKey), FOREIGN KEY(groupId) REFERENCES NodeGroup(groupId));" "CREATE TABLE IF NOT EXISTS NodeRaw(node BLOB NOT NULL, data BLOB NOT NULL, FOREIGN KEY(node) REFERENCES Node(nodeHash));" "CREATE TABLE IF NOT EXISTS NodeAddDataRaw(nodeId INTEGER NOT NULL, nodeAddDataId INTEGER NOT NULL, data BLOB NOT NULL, FOREIGN KEY(nodeId) REFERENCES Node(id), FOREIGN KEY(nodeAddDataId) REFERENCES NodeAddData(id));" "CREATE TABLE IF NOT EXISTS NodeUserActionGap(id INTEGER PRIMARY KEY, nodeUserId INTEGER NOT NULL, start INTEGER NOT NULL, range INTEGER NOT NULL, FOREIGN KEY(nodeUserId) REFERENCES NodeUser(id));" "CREATE TABLE IF NOT EXISTS NodeEncryptedUser(id INTEGER PRIMARY KEY, username BLOB UNIQUE NOT NULL, nonce BLOB NOT NULL, encryptedPassword BLOB NOT NULL);" "CREATE TABLE IF NOT EXISTS NodeEncryptedUserData(node INTEGER NOT NULL, usernameId INTEGER NOT NULL, userPublicKey BLOB NOT NULL, nonce BLOB NOT NULL, nodeAndUserPrivateKeyEncrypted BLOB NOT NULL, FOREIGN KEY(usernameId) REFERENCES NodeEncryptedUser(id), FOREIGN KEY(node, userPublicKey) REFERENCES NodeUser(node, publicKey));" "CREATE UNIQUE INDEX IF NOT EXISTS UniqueUserInNode ON NodeUser(node, publicKey);" "CREATE UNIQUE INDEX IF NOT EXISTS UniqueUserGroupAssoc ON NodeUserGroupAssoc(node, userPublicKey, groupId);" "CREATE UNIQUE INDEX IF NOT EXISTS UniqueEncryptedUserDataInNode ON NodeEncryptedUserData(node, usernameId);"); sqlite_prepare_checked(sqliteDb, "INSERT INTO Node(nodeHash, timestamp, creatorPublicKey, adminGroupId) VALUES(?, ?, ?, ?)", &insertNodeStmt); sqlite_prepare_checked(sqliteDb, "INSERT INTO NodeUser(node, publicKey, latestActionCounter) VALUES(?, ?, 0)", &insertUserStmt); sqlite_prepare_checked(sqliteDb, "INSERT INTO NodeGroup(node, groupId, permissionLevel, permissionFlags) VALUES(?, ?, ?, ?)", &insertGroupStmt); sqlite_prepare_checked(sqliteDb, "INSERT INTO NodeAddData(node, requestHash, operation, timestamp, creatorPublicKey, decrypted, userActionCounter) VALUES(?, ?, ?, ?, ?, ?, ?)", &insertNodeAddDataStmt); sqlite_prepare_checked(sqliteDb, "INSERT OR REPLACE INTO NodeDecryptionKey(node, decryptionKey) VALUES(?, ?)", &setNodeDecryptionKeyStmt); sqlite_prepare_checked(sqliteDb, "SELECT decryptionKey FROM NodeDecryptionKey WHERE node = ?", &getNodeDecryptionKeyStmt); sqlite_prepare_checked(sqliteDb, "INSERT INTO NodeUserGroupAssoc(node, userPublicKey, groupId) VALUES(?, ?, ?)", &insertNodeUserGroupAssocStmt); sqlite_prepare_checked(sqliteDb, "SELECT timestamp, creatorPublicKey, adminGroupId FROM Node WHERE nodeHash = ?", &selectNodeStmt); sqlite_prepare_checked(sqliteDb, "SELECT id, requestHash, operation, timestamp, creatorPublicKey From NodeAddData WHERE node = ?", &selectNodeAddDataByNodeStmt); sqlite_prepare_checked(sqliteDb, "SELECT id FROM Node WHERE nodeHash = ?", &selectNodeIdStatement); sqlite_prepare_checked(sqliteDb, "SELECT id FROM NodeAddData WHERE requestHash = ?", &selectNodeAddDataIdStatement); sqlite_prepare_checked(sqliteDb, "INSERT INTO NodeRaw(node, data) VALUES(?, ?)", &insertNodeRawStmt); sqlite_prepare_checked(sqliteDb, "INSERT INTO NodeAddDataRaw(nodeId, nodeAddDataId, data) VALUES(?, ?, ?)", &insertNodeAddDataRawStmt); sqlite_prepare_checked(sqliteDb, "INSERT INTO NodeAddDataAdditional(nodeAddDataId, data) VALUES(?, ?)", &insertNodeAddDataAdditionalStmt); sqlite_prepare_checked(sqliteDb, "INSERT INTO NodeAddUserData(nodeAddDataId, userToAddPublicKey, groupId) VALUES(?, ?, ?)", &insertNodeAddUserDataStmt); sqlite_prepare_checked(sqliteDb, "SELECT data From NodeAddDataAdditional WHERE nodeAddDataId = ?", &selectNodeAddDataAdditionalStmt); sqlite_prepare_checked(sqliteDb, "SELECT userToAddPublicKey, groupId From NodeAddUserData WHERE nodeAddDataId = ?", &selectNodeAddUserDataStmt); sqlite_prepare_checked(sqliteDb, "UPDATE NodeAddData SET decrypted = ? WHERE id = ?", &setNodeAddDataDecryptedStmt); sqlite_prepare_checked(sqliteDb, "UPDATE NodeAddDataAdditional SET data = ? WHERE nodeAddDataId = ?", &setNodeAddDataAdditionalDataStmt); try { loadMetadataFromFile(); } catch(FileException &e) { Log::warn("Failed to load storage meta data, reason: %s. Ignoring... (new storage probably)", e.what()); sibs::SafeSerializer metadataSerializer; metadataSerializer.add(STORAGE_VERSION); randombytes_buf(passwordSalt, PASSWORD_SALT_LEN); metadataSerializer.add(passwordSalt, PASSWORD_SALT_LEN); fileOverwrite(metadataFilePath, { metadataSerializer.getBuffer().data(), metadataSerializer.getBuffer().size() }); } catch(sibs::DeserializeException &e) { string errMsg = "Failed to load metadata, reason: "; errMsg += e.what(); throw DatabaseStorageCorrupt(errMsg); } try { loadRemoteNodesFromFile(); } catch(exception &e) { string errMsg = "Failed to load nodes, reason: "; errMsg += e.what(); Log::warn("%s", errMsg.c_str()); } } void DatabaseStorage::cleanup() { sqlite3_finalize(insertNodeStmt); sqlite3_finalize(insertUserStmt); sqlite3_finalize(insertGroupStmt); sqlite3_finalize(insertNodeAddDataStmt); sqlite3_finalize(setNodeDecryptionKeyStmt); sqlite3_finalize(getNodeDecryptionKeyStmt); sqlite3_finalize(insertNodeUserGroupAssocStmt); sqlite3_finalize(selectNodeStmt); sqlite3_finalize(selectNodeAddDataByNodeStmt); sqlite3_finalize(selectNodeIdStatement); sqlite3_finalize(selectNodeAddDataIdStatement); sqlite3_finalize(insertNodeRawStmt); sqlite3_finalize(insertNodeAddDataRawStmt); sqlite3_finalize(insertNodeAddDataAdditionalStmt); sqlite3_finalize(insertNodeAddUserDataStmt); sqlite3_finalize(selectNodeAddDataAdditionalStmt); sqlite3_finalize(selectNodeAddUserDataStmt); sqlite3_finalize(setNodeAddDataDecryptedStmt); sqlite3_finalize(setNodeAddDataAdditionalDataStmt); sqlite3_close(sqliteDb); } DatabaseStorage::~DatabaseStorage() { cleanup(); } void DatabaseStorage::loadNode(const Hash &nodeHash, DatabaseLoadOrder loadOrder) { std::lock_guard lock(databaseOperationCallbackMutex); string orderByString; if(loadOrder == DatabaseLoadOrder::OLDEST_FIRST) orderByString = " ORDER BY timestamp ASC"; else if(loadOrder == DatabaseLoadOrder::NEWEST_FIRST) orderByString = " ORDER BY timestamp DESC"; if(database->onCreateNodeCallbackFunc) { string queryStr = "SELECT timestamp, creatorPublicKey, adminGroupId From Node WHERE nodeHash = ?"; queryStr += orderByString; SqlQuery nodeQuery(sqliteDb, queryStr.c_str(), { DataView(nodeHash.getData(), nodeHash.getSize()) }); while(nodeQuery.next()) { u64 timestamp = nodeQuery.getInt64(0); const DataView creatorPublicKeyRaw = nodeQuery.getBlob(1); Signature::PublicKey creatorPublicKey((const char*)creatorPublicKeyRaw.data, creatorPublicKeyRaw.size); u8 adminGroupId[GROUP_ID_LENGTH]; const DataView adminGroupRaw = nodeQuery.getBlob(2); memcpy(adminGroupId, adminGroupRaw.data, GROUP_ID_LENGTH); DatabaseCreateNodeRequest createNodeRequest(&nodeHash, timestamp, &creatorPublicKey, DataView(adminGroupId, GROUP_ID_LENGTH), database); createNodeRequest.loadedFromCache = true; database->onCreateNodeCallbackFunc(createNodeRequest); } } string queryStr = "SELECT id, requestHash, operation, timestamp, creatorPublicKey From NodeAddData WHERE node = ? AND decrypted = 1"; queryStr += orderByString; SqlQuery nodeQuery(sqliteDb, queryStr.c_str(), { DataView(nodeHash.getData(), nodeHash.getSize()) }); while(nodeQuery.next()) { i64 rowId = nodeQuery.getInt64(0); const DataView requestHashRaw = nodeQuery.getBlob(1); Hash requestHash; memcpy(requestHash.getData(), requestHashRaw.data, HASH_BYTE_SIZE); DatabaseOperation operation = (DatabaseOperation)nodeQuery.getInt(2); u64 timestamp = nodeQuery.getInt64(3); const DataView creatorPublicKeyRaw = nodeQuery.getBlob(4); Signature::PublicKey creatorPublicKey((const char*)creatorPublicKeyRaw.data, creatorPublicKeyRaw.size); if(operation == DatabaseOperation::ADD_DATA) { if(!database->onAddNodeCallbackFunc) continue; sqlite3_reset(selectNodeAddDataAdditionalStmt); sqlite3_clear_bindings(selectNodeAddDataAdditionalStmt); int rc; rc = sqlite3_bind_int64(selectNodeAddDataAdditionalStmt, 1, rowId); bindCheckError(rc); rc = sqlite3_step(selectNodeAddDataAdditionalStmt); if(rc != SQLITE_ROW) { string errMsg = "select NodeAddDataAdditional failed with error: "; errMsg += sqlite3_errmsg(sqliteDb); throw DatabaseStorageException(errMsg); } // TODO: There is no need to allocate/deallocate several times, this can be moved outside the while loop const void *decryptedDataRaw = sqlite3_column_blob(selectNodeAddDataAdditionalStmt, 0); int decryptedDataSize = sqlite3_column_bytes(selectNodeAddDataAdditionalStmt, 0); OwnedByteArray decryptedData(new u8[decryptedDataSize], decryptedDataSize); memcpy(decryptedData.data, decryptedDataRaw, decryptedDataSize); DatabaseAddNodeRequest addNodeRequest(&nodeHash, &requestHash, timestamp, &creatorPublicKey, DataView(decryptedData.data, decryptedData.size), database); addNodeRequest.loadedFromCache = true; database->onAddNodeCallbackFunc(addNodeRequest); } else if(operation == DatabaseOperation::ADD_USER) { if(!database->onAddUserCallbackFunc) continue; sqlite3_reset(selectNodeAddUserDataStmt); sqlite3_clear_bindings(selectNodeAddUserDataStmt); int rc; rc = sqlite3_bind_int64(selectNodeAddUserDataStmt, 1, rowId); bindCheckError(rc); rc = sqlite3_step(selectNodeAddUserDataStmt); if(rc != SQLITE_ROW) { string errMsg = "select NodeAddUserData failed with error: "; errMsg += sqlite3_errmsg(sqliteDb); throw DatabaseStorageException(errMsg); } const void *userToAddPublicKeyRaw = sqlite3_column_blob(selectNodeAddUserDataStmt, 0); Signature::PublicKey userToAddPublicKey((const char*)userToAddPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); const void *groupToAddUserToRaw = sqlite3_column_blob(selectNodeAddUserDataStmt, 1); u8 groupToAddUserTo[GROUP_ID_LENGTH]; memcpy(groupToAddUserTo, groupToAddUserToRaw, GROUP_ID_LENGTH); DatabaseAddUserRequest addUserRequest(&nodeHash, &requestHash, timestamp, &creatorPublicKey, &userToAddPublicKey, DataView(groupToAddUserTo, GROUP_ID_LENGTH), database); addUserRequest.loadedFromCache = true; database->onAddUserCallbackFunc(addUserRequest); } } } void DatabaseStorage::loadMetadataFromFile() { OwnedByteArray metadataFileContent = fileGetContent(metadataFilePath); sibs::SafeDeserializer deserializer((u8*)metadataFileContent.data, metadataFileContent.size); u16 storageVersion = deserializer.extract(); if(storageVersion != STORAGE_VERSION) throw std::runtime_error("Wrong storage version!"); deserializer.extract(passwordSalt, PASSWORD_SALT_LEN); assert(deserializer.empty()); } void DatabaseStorage::loadRemoteNodesFromFile() { OwnedByteArray remoteNodesFileContent = fileGetContent(remoteNodesFilePath); remotePeers = sibs::DirectConnectionsUtils::deserializePeers(remoteNodesFileContent.data, remoteNodesFileContent.size); } static void sqlite_step_throw_on_failure(sqlite3 *db, sqlite3_stmt *stmt, const char *description) { int rc = sqlite3_step(stmt); if(rc != SQLITE_DONE) { string errMsg = description; errMsg += " failed with error: "; errMsg += sqlite3_errmsg(db); if(rc == SQLITE_CONSTRAINT) throw DatabaseStorageAlreadyExists(errMsg); else throw DatabaseStorageException(errMsg); } } void DatabaseStorage::bindCheckError(int sqliteBindResult) { if(sqliteBindResult != SQLITE_OK) { string errMsg = "Failed to bind param, error code: "; errMsg += to_string(sqliteBindResult); throw DatabaseStorageException(errMsg); } } i64 DatabaseStorage::getNodeRowId(const Hash &nodeHash) { sqlite3_reset(selectNodeIdStatement); sqlite3_clear_bindings(selectNodeIdStatement); int rc; rc = sqlite3_bind_blob(selectNodeIdStatement, 1, nodeHash.getData(), nodeHash.getSize(), SQLITE_STATIC); bindCheckError(rc); rc = sqlite3_step(selectNodeIdStatement); if(rc != SQLITE_ROW) { string errMsg = "select Node id failed with error: "; errMsg += sqlite3_errmsg(sqliteDb); throw DatabaseStorageException(errMsg); } return sqlite3_column_int64(selectNodeIdStatement, 0); } i64 DatabaseStorage::getNodeAddDataRowId(const Hash &requestHash) { sqlite3_reset(selectNodeAddDataIdStatement); sqlite3_clear_bindings(selectNodeAddDataIdStatement); int rc; rc = sqlite3_bind_blob(selectNodeAddDataIdStatement, 1, requestHash.getData(), requestHash.getSize(), SQLITE_STATIC); bindCheckError(rc); rc = sqlite3_step(selectNodeAddDataIdStatement); if(rc != SQLITE_ROW) { string errMsg = "select NodeAddData id failed with error: "; errMsg += sqlite3_errmsg(sqliteDb); throw DatabaseStorageException(errMsg); } return sqlite3_column_int64(selectNodeAddDataIdStatement, 0); } bool DatabaseStorage::doesNodeExist(const Hash &nodeHash) const { SqlQuery query(sqliteDb, "SELECT id FROM Node WHERE nodeHash = ?", { DataView(nodeHash.getData(), nodeHash.getSize()) }); return query.next(); } bool DatabaseStorage::doesDataExist(const Hash &requestHash) const { SqlQuery query(sqliteDb, "SELECT id FROM NodeAddData WHERE requestHash = ?", { DataView(requestHash.getData(), requestHash.getSize()) }); return query.next(); } void DatabaseStorage::createStorage(const Hash &hash, const Signature::PublicKey &adminPublicKey, const DataView &adminGroupId, u64 timestamp, const void *data, usize size) { SqlTransaction transaction(sqliteDb); { sqlite3_reset(insertNodeStmt); sqlite3_clear_bindings(insertNodeStmt); int rc; rc = sqlite3_bind_blob(insertNodeStmt, 1, hash.getData(), hash.getSize(), SQLITE_STATIC); bindCheckError(rc); rc = sqlite3_bind_int64(insertNodeStmt, 2, timestamp); bindCheckError(rc); rc = sqlite3_bind_blob(insertNodeStmt, 3, adminPublicKey.getData(), adminPublicKey.getSize(), SQLITE_STATIC); bindCheckError(rc); rc = sqlite3_bind_blob(insertNodeStmt, 4, adminGroupId.data, GROUP_ID_LENGTH, SQLITE_STATIC); bindCheckError(rc); sqlite_step_throw_on_failure(sqliteDb, insertNodeStmt, "insert data into Node"); addGroup(hash, adminGroupId, ADMIN_PERMISSION); addUser(hash, adminPublicKey, adminGroupId); } { sqlite3_reset(insertNodeRawStmt); sqlite3_clear_bindings(insertNodeRawStmt); int rc; rc = sqlite3_bind_blob(insertNodeRawStmt, 1, hash.getData(), hash.getSize(), SQLITE_STATIC); bindCheckError(rc); rc = sqlite3_bind_blob(insertNodeRawStmt, 2, data, size, SQLITE_STATIC); bindCheckError(rc); sqlite_step_throw_on_failure(sqliteDb, insertNodeRawStmt, "insert data into NodeRaw"); } transaction.commit(); auto nodeDecryptionKeyResult = getNodeDecryptionKey(hash); if(nodeDecryptionKeyResult.first) decryptNodeData(hash, nodeDecryptionKeyResult.second, &adminPublicKey, adminGroupId, timestamp); } void DatabaseStorage::appendStorage(const Hash &nodeHash, const Hash &dataHash, DatabaseOperation operation, u64 newUserActionCounter, const Signature::PublicKey &creatorPublicKey, u64 timestamp, const void *data, usize size, const DataView &additionalDataView) { SqlTransaction transaction(sqliteDb); { SqlQuery selectUserIdAndActionCounter(sqliteDb, "SELECT id, latestActionCounter FROM NodeUser WHERE node = ? AND publicKey = ?", { DataView(nodeHash.getData(), nodeHash.getSize()), DataView((void*)creatorPublicKey.getData(), creatorPublicKey.getSize()) }); if(!selectUserIdAndActionCounter.next()) { string errMsg = "User "; errMsg += creatorPublicKey.toString(); errMsg += " not found in node "; errMsg += nodeHash.toString(); throw DatabaseStorageNotFound(errMsg); } i64 nodeUserRowId = selectUserIdAndActionCounter.getInt64(0); u64 userActionCounter = selectUserIdAndActionCounter.getInt64(1); if(newUserActionCounter == userActionCounter) { throw DatabaseStorageException("Got unique package but action counter was equal to users existing one, discarding packet"); } else if(newUserActionCounter == userActionCounter + 1) { SqlExec setUserActionCounter(sqliteDb, "UPDATE NodeUser SET latestActionCounter = ? WHERE id = ?"); setUserActionCounter.execWithArgs({ newUserActionCounter, nodeUserRowId }); } else { SqlQuery existingActionGap(sqliteDb, "SELECT id, start, range FROM NodeUserActionGap WHERE nodeUserId = ? AND ? >= start AND ? <= start + range", { nodeUserRowId, newUserActionCounter, newUserActionCounter }); if(existingActionGap.next()) { i64 actionGapRowId = existingActionGap.getInt64(0); u64 start = existingActionGap.getInt64(1); u64 range = existingActionGap.getInt64(2); SqlExec removeRange(sqliteDb, "DELETE FROM NodeUserActionGap WHERE id = ?"); removeRange.execWithArgs({ actionGapRowId }); if(range == 1) { if(start + range > userActionCounter) { SqlExec setUserActionCounter(sqliteDb, "UPDATE NodeUser SET latestActionCounter = ? WHERE id = ?"); setUserActionCounter.execWithArgs({ start + range, nodeUserRowId }); } } else { SqlExec addUserActionGap(sqliteDb, "INSERT INTO NodeUserActionGap(nodeUserId, start, range) VALUES(?, ?, ?)"); u64 startBefore = start; u64 rangeBefore = newUserActionCounter - start; if(rangeBefore > 0) addUserActionGap.execWithArgs({ nodeUserRowId, startBefore, rangeBefore }); u64 startAfter = newUserActionCounter + 1; u64 rangeAfter = (start + range) - newUserActionCounter; if(rangeAfter > 0) addUserActionGap.execWithArgs({ nodeUserRowId, startAfter, rangeAfter }); } } else { if(newUserActionCounter > userActionCounter + 1) { u64 start = userActionCounter + 1; u64 range = newUserActionCounter - start; SqlExec addUserActionGap(sqliteDb, "INSERT INTO NodeUserActionGap(nodeUserId, start, range) VALUES(?, ?, ?)"); addUserActionGap.execWithArgs({ nodeUserRowId, start, range }); } else if(newUserActionCounter < userActionCounter) { u64 start = newUserActionCounter; u64 range = userActionCounter - start; SqlExec addUserActionGap(sqliteDb, "INSERT INTO NodeUserActionGap(nodeUserId, start, range) VALUES(?, ?, ?)"); addUserActionGap.execWithArgs({ nodeUserRowId, start, range }); } } } } { sqlite3_reset(insertNodeAddDataStmt); sqlite3_clear_bindings(insertNodeAddDataStmt); int rc; rc = sqlite3_bind_blob(insertNodeAddDataStmt, 1, nodeHash.getData(), nodeHash.getSize(), SQLITE_STATIC); bindCheckError(rc); rc = sqlite3_bind_blob(insertNodeAddDataStmt, 2, dataHash.getData(), dataHash.getSize(), SQLITE_STATIC); bindCheckError(rc); rc = sqlite3_bind_int(insertNodeAddDataStmt, 3, (u8)operation); bindCheckError(rc); rc = sqlite3_bind_int64(insertNodeAddDataStmt, 4, timestamp); bindCheckError(rc); rc = sqlite3_bind_blob(insertNodeAddDataStmt, 5, creatorPublicKey.getData(), creatorPublicKey.getSize(), SQLITE_STATIC); bindCheckError(rc); rc = sqlite3_bind_int(insertNodeAddDataStmt, 6, 0); bindCheckError(rc); rc = sqlite3_bind_int(insertNodeAddDataStmt, 7, newUserActionCounter); bindCheckError(rc); sqlite_step_throw_on_failure(sqliteDb, insertNodeAddDataStmt, "insert data into NodeAddData"); } i64 nodeRowId = getNodeRowId(nodeHash); i64 nodeAddRowId = getNodeAddDataRowId(dataHash); Signature::PublicKey userToAddPublicKey; u8 groupToAddUserTo[GROUP_ID_LENGTH]; if(operation == DatabaseOperation::ADD_DATA) { sqlite3_reset(insertNodeAddDataAdditionalStmt); sqlite3_clear_bindings(insertNodeAddDataAdditionalStmt); int rc; rc = sqlite3_bind_int64(insertNodeAddDataAdditionalStmt, 1, nodeAddRowId); bindCheckError(rc); rc = sqlite3_bind_blob(insertNodeAddDataAdditionalStmt, 2, additionalDataView.data, additionalDataView.size, SQLITE_STATIC); bindCheckError(rc); sqlite_step_throw_on_failure(sqliteDb, insertNodeAddDataAdditionalStmt, "insert data into NodeAddDataAdditional"); } else if(operation == DatabaseOperation::ADD_USER) { sibs::SafeDeserializer deserializer((const u8*)additionalDataView.data, additionalDataView.size); deserializer.extract((u8*)userToAddPublicKey.getData(), PUBLIC_KEY_NUM_BYTES); deserializer.extract(groupToAddUserTo, GROUP_ID_LENGTH); sqlite3_reset(insertNodeAddUserDataStmt); sqlite3_clear_bindings(insertNodeAddUserDataStmt); int rc; rc = sqlite3_bind_int64(insertNodeAddUserDataStmt, 1, nodeAddRowId); bindCheckError(rc); rc = sqlite3_bind_blob(insertNodeAddUserDataStmt, 2, userToAddPublicKey.getData(), PUBLIC_KEY_NUM_BYTES, SQLITE_STATIC); bindCheckError(rc); rc = sqlite3_bind_blob(insertNodeAddUserDataStmt, 3, groupToAddUserTo, GROUP_ID_LENGTH, SQLITE_STATIC); bindCheckError(rc); sqlite_step_throw_on_failure(sqliteDb, insertNodeAddUserDataStmt, "insert data into NodeAddUserData"); } else { throw ("Unexpected operation type"); } { sqlite3_reset(insertNodeAddDataRawStmt); sqlite3_clear_bindings(insertNodeAddDataRawStmt); int rc; rc = sqlite3_bind_int64(insertNodeAddDataRawStmt, 1, nodeRowId); bindCheckError(rc); rc = sqlite3_bind_int64(insertNodeAddDataRawStmt, 2, nodeAddRowId); bindCheckError(rc); rc = sqlite3_bind_blob(insertNodeAddDataRawStmt, 3, data, size, SQLITE_STATIC); bindCheckError(rc); sqlite_step_throw_on_failure(sqliteDb, insertNodeAddDataRawStmt, "insert data into NodeAddDataRaw"); } auto nodeDecryptionKeyResult = getNodeDecryptionKey(nodeHash); if(nodeDecryptionKeyResult.first) { if(operation == DatabaseOperation::ADD_DATA) decryptNodeAddData(nodeAddRowId, nodeHash, dataHash, timestamp, &creatorPublicKey, additionalDataView, nodeDecryptionKeyResult.second); else if(operation == DatabaseOperation::ADD_USER) decryptNodeAddUser(nodeAddRowId, nodeHash, dataHash, timestamp, &creatorPublicKey, &userToAddPublicKey, DataView(groupToAddUserTo, GROUP_ID_LENGTH), nodeDecryptionKeyResult.second); } transaction.commit(); } void DatabaseStorage::addGroup(const Hash &nodeHash, const DataView &groupId, const Permission &permissions) { sqlite3_reset(insertGroupStmt); sqlite3_clear_bindings(insertGroupStmt); int rc; rc = sqlite3_bind_blob(insertGroupStmt, 1, nodeHash.getData(), nodeHash.getSize(), SQLITE_STATIC); bindCheckError(rc); rc = sqlite3_bind_blob(insertGroupStmt, 2, groupId.data, GROUP_ID_LENGTH, SQLITE_STATIC); bindCheckError(rc); rc = sqlite3_bind_int(insertGroupStmt, 3, permissions.getPermissionLevel()); bindCheckError(rc); rc = sqlite3_bind_int64(insertGroupStmt, 4, permissions.getPermissionFlags()); bindCheckError(rc); sqlite_step_throw_on_failure(sqliteDb, insertGroupStmt, "insert data into NodeGroup"); Log::debug("Created group %s in node %s", bin2hex((const char*)groupId.data, GROUP_ID_LENGTH).c_str(), nodeHash.toString().c_str()); } void DatabaseStorage::addUserToGroup(const Hash &nodeHash, const Signature::PublicKey &userPublicKey, const DataView &groupId) { sqlite3_reset(insertNodeUserGroupAssocStmt); sqlite3_clear_bindings(insertNodeUserGroupAssocStmt); int rc; rc = sqlite3_bind_blob(insertNodeUserGroupAssocStmt, 1, nodeHash.getData(), nodeHash.getSize(), SQLITE_STATIC); bindCheckError(rc); rc = sqlite3_bind_blob(insertNodeUserGroupAssocStmt, 2, userPublicKey.getData(), userPublicKey.getSize(), SQLITE_STATIC); bindCheckError(rc); rc = sqlite3_bind_blob(insertNodeUserGroupAssocStmt, 3, groupId.data, groupId.size, SQLITE_STATIC); bindCheckError(rc); sqlite_step_throw_on_failure(sqliteDb, insertNodeUserGroupAssocStmt, "insert data into NodeUserGroupAssoc"); } void DatabaseStorage::addUser(const Hash &nodeHash, const Signature::PublicKey &userPublicKey, const DataView &groupId) { sqlite3_reset(insertUserStmt); sqlite3_clear_bindings(insertUserStmt); int rc; rc = sqlite3_bind_blob(insertUserStmt, 1, nodeHash.getData(), nodeHash.getSize(), SQLITE_STATIC); bindCheckError(rc); rc = sqlite3_bind_blob(insertUserStmt, 2, userPublicKey.getData(), userPublicKey.getSize(), SQLITE_STATIC); bindCheckError(rc); try { sqlite_step_throw_on_failure(sqliteDb, insertUserStmt, "insert data into NodeUser"); } catch(DatabaseStorageAlreadyExists &e) { // User already exists in node, error can be ignored and instead we can continue to add user to group } addUserToGroup(nodeHash, userPublicKey, groupId); Log::debug("Created user %s in node %s", userPublicKey.toString().c_str(), nodeHash.toString().c_str()); } void DatabaseStorage::fetchNodeRaw(const Hash &nodeHash, FetchNodeRawCallbackFunc callbackFunc) { SqlQuery query(sqliteDb, "SELECT data FROM NodeRaw WHERE node = ?", { DataView(nodeHash.getData(), nodeHash.getSize()) }); while(query.next()) { const DataView rawData = query.getBlob(0); callbackFunc(rawData); } } void DatabaseStorage::fetchNodeAddDataRaw(const Hash &nodeHash, FetchNodeAddDataRawCallbackFunc callbackFunc, DatabaseFetchOrder fetchOrder) { string orderByString; if(fetchOrder == DatabaseFetchOrder::OLDEST_FIRST) orderByString = " ORDER BY nodeAddData.timestamp ASC"; else if(fetchOrder == DatabaseFetchOrder::NEWEST_FIRST) orderByString = " ORDER BY nodeAddData.timestamp DESC"; string queryStr = "SELECT rawData.data, nodeAddData.creatorPublicKey, nodeAddData.userActionCounter From NodeAddData AS nodeAddData INNER JOIN NodeAddDataRaw AS rawData ON rawData.nodeAddDataId = nodeAddData.id WHERE nodeAddData.node = ?"; queryStr += orderByString; SqlQuery query(sqliteDb, queryStr.c_str(), { DataView(nodeHash.getData(), nodeHash.getSize()) }); while(query.next()) { const DataView rawData = query.getBlob(0); const DataView creatorPublicKey = query.getBlob(1); u64 userActionCounter = query.getInt64(2); callbackFunc(rawData, creatorPublicKey, userActionCounter); } } void DatabaseStorage::fetchNodeUserActionGaps(const Hash &nodeHash, FetchNodeUserActionGapsCallbackFunc callbackFunc) { SqlQuery query(sqliteDb, "SELECT user.publicKey, actionGap.start, actionGap.range FROM NodeUser AS user INNER JOIN NodeUserActionGap AS actionGap ON actionGap.nodeUserId = user.id WHERE user.node = ?", { DataView(nodeHash.getData(), nodeHash.getSize()) }); while(query.next()) { const DataView userPublicKey = query.getBlob(0); u64 actionGapStart = query.getInt64(1); u64 actionGapRange = query.getInt64(2); callbackFunc(userPublicKey, actionGapStart, actionGapRange); } } void DatabaseStorage::fetchNodeUserLatestActionCounter(const Hash &nodeHash, FetchNodeUserLatestActionCounterCallbackFunc callbackFunc) { SqlQuery query(sqliteDb, "SELECT publicKey, latestActionCounter FROM NodeUser WHERE node = ?", { DataView(nodeHash.getData(), nodeHash.getSize()) }); while(query.next()) { const DataView userPublicKey = query.getBlob(0); u64 latestActionCounter = query.getInt64(1); callbackFunc(userPublicKey, latestActionCounter); } } bool DatabaseStorage::isUserAllowedToAddDataInNode(const Hash &nodeHash, const Signature::PublicKey &userPublicKey) const { SqlQuery queryCreatorGroupWithRightsToAddData(sqliteDb, "SELECT nodeGroup.rowid FROM NodeUserGroupAssoc AS userGroupAssoc" " INNER JOIN NodeGroup AS nodeGroup ON nodeGroup.groupId = userGroupAssoc.groupId" " WHERE userGroupAssoc.node = ? AND userGroupAssoc.userPublicKey = ? AND (nodeGroup.permissionFlags & ?) != 0", { DataView(nodeHash.getData(), nodeHash.getSize()), DataView((void*)userPublicKey.getData(), userPublicKey.getSize()), (i64)PermissionType::ADD_DATA }); return queryCreatorGroupWithRightsToAddData.next(); } bool DatabaseStorage::isUserAllowedToAddUserToGroupInNode(const Hash &nodeHash, const Signature::PublicKey &userPublicKey, const DataView &groupToAddUserTo) const { SqlQuery queryGroupToAdd(sqliteDb, "SELECT permissionLevel FROM NodeGroup WHERE groupId = ?", { groupToAddUserTo }); if(!queryGroupToAdd.next()) { // TODO: Add to quarantine? Log::error("There is no group with id %s in node %s", bin2hex((const char*)groupToAddUserTo.data, groupToAddUserTo.size).c_str(), nodeHash.toString().c_str()); return false; } int groupToAddPermissionLevel = queryGroupToAdd.getInt(0); SqlQuery queryCreatorGroupWithRightsToAddUserToGroup(sqliteDb, "SELECT nodeGroup.rowid FROM NodeUserGroupAssoc AS userGroupAssoc" " INNER JOIN NodeGroup AS nodeGroup ON nodeGroup.groupId = userGroupAssoc.groupId" " WHERE userGroupAssoc.node = ? AND userGroupAssoc.userPublicKey = ? AND (nodeGroup.permissionLevel = ? AND ((nodeGroup.permissionFlags & ?) != 0) OR (nodeGroup.permissionLevel > ? AND (nodeGroup.permissionFlags & ?) != 0))", { DataView(nodeHash.getData(), nodeHash.getSize()), DataView((void*)userPublicKey.getData(), userPublicKey.getSize()), groupToAddPermissionLevel, (i64)PermissionType::ADD_USER_SAME_LEVEL, groupToAddPermissionLevel, (i64)PermissionType::ADD_USER_HIGHER_LEVEL }); return queryCreatorGroupWithRightsToAddUserToGroup.next(); } int DatabaseStorage::getUserLowestPermissionLevel(const Hash &nodeHash, const Signature::PublicKey &userPublicKey) const { SqlQuery query(sqliteDb, "SELECT MIN(nodeGroup.permissionLevel) FROM NodeUserGroupAssoc AS userGroupAssoc" " INNER JOIN NodeGroup AS nodeGroup ON nodeGroup.groupId = userGroupAssoc.groupId" " WHERE userGroupAssoc.node = ? AND userGroupAssoc.userPublicKey = ?", { DataView(nodeHash.getData(), nodeHash.getSize()), DataView((void*)userPublicKey.getData(), userPublicKey.getSize()) }); if(!query.next()) { Log::error("There is no node %s, no user %s in that node or user does not belong to any groups", nodeHash.toString().c_str(), userPublicKey.toString().c_str()); return -1; } return query.getInt(0); } u64 DatabaseStorage::getUserActionCounter(const Hash &nodeHash, const Signature::PublicKey &userPublicKey) const { SqlQuery query(sqliteDb, "SELECT latestActionCounter FROM NodeUser WHERE node = ? AND publicKey = ?", { DataView(nodeHash.getData(), nodeHash.getSize()), DataView((void*)userPublicKey.getData(), userPublicKey.getSize()) }); if(!query.next()) { string errMsg = "User "; errMsg += userPublicKey.toString(); errMsg += " not found in node "; errMsg += nodeHash.toString(); throw DatabaseStorageNotFound(errMsg); } return query.getInt64(0); } bool DatabaseStorage::doesStoredUserExist(const string &username) const { SqlQuery query(sqliteDb, "SELECT id from NodeEncryptedUser WHERE username = ?", { DataView((void*)username.data(), username.size()) }); return query.next(); } void DatabaseStorage::storeUserWithoutNodes(const string &username, const string &password) { OwnedByteArray hashedPassword = hashPassword(DataView((void*)password.data(), password.size()), DataView((void*)passwordSalt, PASSWORD_SALT_LEN)); DataView hashedPasswordView(hashedPassword.data, hashedPassword.size); DataView usernameView((void*)username.data(), username.size()); DataView saltView((void*)passwordSalt, PASSWORD_SALT_LEN); Encryption encryptedPassword(saltView, hashedPasswordView); SqlExec addUserSql(sqliteDb, "INSERT INTO NodeEncryptedUser(username, nonce, encryptedPassword) VALUES(?, ?, ?)"); addUserSql.execWithArgs({ usernameView, encryptedPassword.getNonce(), encryptedPassword.getCipherText() }); } i64 DatabaseStorage::getStoredUserId(const string &username, const DataView &hashedPassword) const { DataView usernameView((void*)username.data(), username.size()); SqlQuery userQuery(sqliteDb, "SELECT id, nonce, encryptedPassword FROM NodeEncryptedUser WHERE username = ?", { usernameView }); if(!userQuery.next()) { string errMsg = "User with name "; errMsg += username; errMsg += " doesn't exist in storage"; throw DatabaseStorageNoSuchStoredUser(errMsg); } i64 encryptedUserRowId = userQuery.getInt64(0); const DataView storedEncryptedPasswordNonce = userQuery.getBlob(1); const DataView storedEncryptedPassword = userQuery.getBlob(2); try { Decryption decryptedStoredPassword(storedEncryptedPassword, storedEncryptedPasswordNonce, hashedPassword); return encryptedUserRowId; } catch(DecryptionException &e) { throw DatabaseStorageWrongPassword(e.what()); } } void DatabaseStorage::storeNodeInfoForUserEncrypted(const DatabaseNode &nodeInfo, const string &username, const string &password, const Signature::KeyPair &keyPair) { OwnedByteArray hashedPassword = hashPassword(DataView((void*)password.data(), password.size()), DataView((void*)passwordSalt, PASSWORD_SALT_LEN)); DataView hashedPasswordView(hashedPassword.data, hashedPassword.size); DataView privateKeyView((void*)keyPair.getPrivateKey().getData(), PRIVATE_KEY_NUM_BYTES); sibs::SafeSerializer serializer; serializer.add((const u8*)privateKeyView.data, privateKeyView.size); serializer.add((const u8*)nodeInfo.getNodeEncryptionKey()->data, nodeInfo.getNodeEncryptionKey()->size); Encryption encryptedNodeUserPrivateKey(DataView(serializer.getBuffer().data(), serializer.getBuffer().size()), hashedPasswordView); SqlTransaction transaction(sqliteDb); DataView usernameView((void*)username.data(), username.size()); SqlQuery userQuery(sqliteDb, "SELECT id, nonce, encryptedPassword FROM NodeEncryptedUser WHERE username = ?", { usernameView }); i64 encryptedUserRowId; try { encryptedUserRowId = getStoredUserId(username, hashedPasswordView); } catch(DatabaseStorageNoSuchStoredUser &e) { DataView saltView((void*)passwordSalt, PASSWORD_SALT_LEN); Encryption encryptedPassword(saltView, hashedPasswordView); SqlExec addUserSql(sqliteDb, "INSERT INTO NodeEncryptedUser(username, nonce, encryptedPassword) VALUES(?, ?, ?)"); addUserSql.execWithArgs({ usernameView, encryptedPassword.getNonce(), encryptedPassword.getCipherText() }); SqlQuery userQuery(sqliteDb, "SELECT id FROM NodeEncryptedUser WHERE username = ?", { usernameView }); if(!userQuery.next()) throw DatabaseStorageException("Unexpected error when retrieving stored user"); encryptedUserRowId = userQuery.getInt64(0); } SqlExec sqlExec(sqliteDb, "INSERT INTO NodeEncryptedUserData(node, usernameId, userPublicKey, nonce, nodeAndUserPrivateKeyEncrypted) VALUES(?, ?, ?, ?, ?)"); sqlExec.execWithArgs({ DataView(nodeInfo.getRequestHash()->getData(), nodeInfo.getRequestHash()->getSize()), encryptedUserRowId, DataView((void*)keyPair.getPublicKey().getData(), PUBLIC_KEY_NUM_BYTES), encryptedNodeUserPrivateKey.getNonce(), encryptedNodeUserPrivateKey.getCipherText() }); transaction.commit(); } MapHash DatabaseStorage::getStoredNodeUserInfoDecrypted(const string &username, const string &password) const { OwnedByteArray hashedPassword = hashPassword(DataView((void*)password.data(), password.size()), DataView((void*)passwordSalt, PASSWORD_SALT_LEN)); DataView hashedPasswordView(hashedPassword.data, hashedPassword.size); i64 encryptedUserRowId = getStoredUserId(username, hashedPasswordView); MapHash result; SqlQuery query(sqliteDb, "SELECT node, userPublicKey, nonce, nodeAndUserPrivateKeyEncrypted FROM NodeEncryptedUserData WHERE usernameId = ?", { encryptedUserRowId }); while(query.next()) { Hash nodeHash; const DataView storedNodeHash = query.getBlob(0); memcpy(nodeHash.getData(), storedNodeHash.data, HASH_BYTE_SIZE); const DataView storedUserPublicKey = query.getBlob(1); Signature::PublicKey userPublicKey((const char*)storedUserPublicKey.data, storedUserPublicKey.size); const DataView storedNonce = query.getBlob(2); const DataView storedNodeUserPrivateKeyEncrypted = query.getBlob(3); try { // TODO: We can bypass decrypting several times by storing several nodes private key user the same field (as a blob) Decryption decryptedStoredNodeUserPrivateKey(storedNodeUserPrivateKeyEncrypted, storedNonce, hashedPasswordView); if(decryptedStoredNodeUserPrivateKey.getDecryptedText().size != PRIVATE_KEY_NUM_BYTES + ENCRYPTION_KEY_BYTE_SIZE) throw DatabaseStorageException("Encrypted data size is of unexpected size"); Signature::PrivateKey userPrivateKey((const char*)decryptedStoredNodeUserPrivateKey.getDecryptedText().data, PRIVATE_KEY_NUM_BYTES); shared_ptr keyPair = make_shared(userPublicKey, userPrivateKey); shared_ptr nodeEncryptionKey = make_shared(new u8[ENCRYPTION_KEY_BYTE_SIZE], ENCRYPTION_KEY_BYTE_SIZE); memcpy(nodeEncryptionKey->data, (char*)decryptedStoredNodeUserPrivateKey.getDecryptedText().data + PRIVATE_KEY_NUM_BYTES, ENCRYPTION_KEY_BYTE_SIZE); result[nodeHash] = { nodeEncryptionKey, keyPair }; } catch(DecryptionException &e) { throw DatabaseStorageWrongPassword(e.what()); } } return result; } pair> DatabaseStorage::getNodeDecryptionKey(const Hash &nodeHash) { sqlite3_reset(getNodeDecryptionKeyStmt); sqlite3_clear_bindings(getNodeDecryptionKeyStmt); int rc; rc = sqlite3_bind_blob(getNodeDecryptionKeyStmt, 1, nodeHash.getData(), nodeHash.getSize(), SQLITE_STATIC); bindCheckError(rc); rc = sqlite3_step(getNodeDecryptionKeyStmt); if(rc != SQLITE_ROW) return make_pair(false, make_shared()); const void *decryptionKeyRaw = sqlite3_column_blob(getNodeDecryptionKeyStmt, 0); u8 *decryptionKeyRawCopy = new u8[ENCRYPTION_KEY_BYTE_SIZE]; memcpy(decryptionKeyRawCopy, decryptionKeyRaw, ENCRYPTION_KEY_BYTE_SIZE); shared_ptr decryptionKey = make_shared(decryptionKeyRawCopy, ENCRYPTION_KEY_BYTE_SIZE); return make_pair(true, decryptionKey); } void DatabaseStorage::setNodeDecryptionKey(const Hash &nodeHash, const DataView &decryptionKeyView) { auto nodeDecryptionKeyResult = getNodeDecryptionKey(nodeHash); bool nodeHasExistingEncryptionKey = nodeDecryptionKeyResult.first; sqlite3_reset(setNodeDecryptionKeyStmt); sqlite3_clear_bindings(setNodeDecryptionKeyStmt); int rc; rc = sqlite3_bind_blob(setNodeDecryptionKeyStmt, 1, nodeHash.getData(), nodeHash.getSize(), SQLITE_STATIC); bindCheckError(rc); rc = sqlite3_bind_blob(setNodeDecryptionKeyStmt, 2, decryptionKeyView.data, decryptionKeyView.size, SQLITE_STATIC); bindCheckError(rc); sqlite_step_throw_on_failure(sqliteDb, setNodeDecryptionKeyStmt, "insert or replace node decryption key"); // When changing existing encryption key, do not decrypt the existing data as it has already been decrypted, // the new key should only be used for new data if(!nodeHasExistingEncryptionKey) decryptNodeData(nodeHash, nodeDecryptionKeyResult.second); } const vector>& DatabaseStorage::getRemotePeers() const { return remotePeers; } void DatabaseStorage::setRemotePeers(const std::vector> &remotePeers) { Log::debug("Storing %u remote peers", remotePeers.size()); this->remotePeers = remotePeers; std::vector serializedPeers = sibs::DirectConnectionsUtils::serializePeers(remotePeers); fileOverwrite(remoteNodesFilePath, DataView(serializedPeers.data(), serializedPeers.size())); } vector DatabaseStorage::getUserGroups(const Hash &nodeHash, const Signature::PublicKey &userPublicKey) const { vector result; SqlQuery query(sqliteDb, "SELECT groupId FROM NodeUserGroupAssoc WHERE node = ? AND userPublicKey = ?", { DataView(nodeHash.getData(), nodeHash.getSize()), DataView((void*)userPublicKey.getData(), userPublicKey.getSize()) }); while(query.next()) { const DataView groupIdRaw = query.getBlob(0); OwnedByteArray groupId(new u8[groupIdRaw.size], groupIdRaw.size); memcpy(groupId.data, groupIdRaw.data, groupIdRaw.size); result.emplace_back(move(groupId)); } return result; } bool DatabaseStorage::decryptNodeData(const Hash &nodeHash, const shared_ptr decryptionKey) { sqlite3_reset(selectNodeStmt); sqlite3_clear_bindings(selectNodeStmt); int rc; rc = sqlite3_bind_blob(selectNodeStmt, 1, nodeHash.getData(), nodeHash.getSize(), SQLITE_STATIC); bindCheckError(rc); rc = sqlite3_step(selectNodeStmt); if(rc == SQLITE_DONE) return true; else if(rc != SQLITE_ROW) { string errMsg = "select node failed with error: "; errMsg += sqlite3_errmsg(sqliteDb); throw DatabaseStorageException(errMsg); } u64 timestamp = sqlite3_column_int64(selectNodeStmt, 0); const void *creatorPublicKeyRaw = sqlite3_column_blob(selectNodeStmt, 1); Signature::PublicKey creatorPublicKey((const char*)creatorPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); const void *adminGroupIdRaw = sqlite3_column_blob(selectNodeStmt, 2); u8 adminGroup[GROUP_ID_LENGTH]; memcpy(adminGroup, adminGroupIdRaw, GROUP_ID_LENGTH); return decryptNodeData(nodeHash, decryptionKey, &creatorPublicKey, DataView(adminGroup, GROUP_ID_LENGTH), timestamp); } bool DatabaseStorage::decryptNodeData(const Hash &nodeHash, const shared_ptr decryptionKey, const Signature::PublicKey *creatorPublicKey, const DataView &adminGroupId, u64 timestamp) { const DatabaseCreateNodeRequest createNodeRequest(&nodeHash, timestamp, creatorPublicKey, adminGroupId, database); if(database->onCreateNodeCallbackFunc) { std::lock_guard lock(databaseOperationCallbackMutex); database->onCreateNodeCallbackFunc(createNodeRequest); } sqlite3_reset(selectNodeAddDataByNodeStmt); sqlite3_clear_bindings(selectNodeAddDataByNodeStmt); int rc; rc = sqlite3_bind_blob(selectNodeAddDataByNodeStmt, 1, nodeHash.getData(), nodeHash.getSize(), SQLITE_STATIC); bindCheckError(rc); SqlTransaction transaction(sqliteDb); bool success = true; while(true) { rc = sqlite3_step(selectNodeAddDataByNodeStmt); if(rc == SQLITE_ROW) { i64 rowId = sqlite3_column_int64(selectNodeAddDataByNodeStmt, 0); const void *requestHashRaw = sqlite3_column_blob(selectNodeAddDataByNodeStmt, 1); Hash requestHash; memcpy(requestHash.getData(), requestHashRaw, HASH_BYTE_SIZE); DatabaseOperation operation = (DatabaseOperation)sqlite3_column_int(selectNodeAddDataByNodeStmt, 2); u64 timestamp = sqlite3_column_int64(selectNodeAddDataByNodeStmt, 3); const void *creatorPublicKeyRaw = sqlite3_column_blob(selectNodeAddDataByNodeStmt, 4); Signature::PublicKey creatorPublicKey((const char*)creatorPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); if(operation == DatabaseOperation::ADD_DATA) { sqlite3_reset(selectNodeAddDataAdditionalStmt); sqlite3_clear_bindings(selectNodeAddDataAdditionalStmt); int rc; rc = sqlite3_bind_int64(selectNodeAddDataAdditionalStmt, 1, rowId); bindCheckError(rc); rc = sqlite3_step(selectNodeAddDataAdditionalStmt); if(rc != SQLITE_ROW) { string errMsg = "select NodeAddDataAdditional failed with error: "; errMsg += sqlite3_errmsg(sqliteDb); throw DatabaseStorageException(errMsg); } // TODO: There is no need to allocate/deallocate several times, this can be moved outside the while loop const void *encryptedDataRaw = sqlite3_column_blob(selectNodeAddDataAdditionalStmt, 0); int encryptedDataSize = sqlite3_column_bytes(selectNodeAddDataAdditionalStmt, 0); OwnedByteArray encryptedData(new u8[encryptedDataSize], encryptedDataSize); memcpy(encryptedData.data, encryptedDataRaw, encryptedDataSize); bool appendObjectResult = decryptNodeAddData(rowId, nodeHash, requestHash, timestamp, &creatorPublicKey, DataView(encryptedData.data, encryptedData.size), decryptionKey); if(!appendObjectResult) success = false; } else if(operation == DatabaseOperation::ADD_USER) { sqlite3_reset(selectNodeAddUserDataStmt); sqlite3_clear_bindings(selectNodeAddUserDataStmt); int rc; rc = sqlite3_bind_int64(selectNodeAddUserDataStmt, 1, rowId); bindCheckError(rc); rc = sqlite3_step(selectNodeAddUserDataStmt); if(rc != SQLITE_ROW) { string errMsg = "select NodeAddUserData failed with error: "; errMsg += sqlite3_errmsg(sqliteDb); throw DatabaseStorageException(errMsg); } const void *userToAddPublicKeyRaw = sqlite3_column_blob(selectNodeAddUserDataStmt, 0); Signature::PublicKey userToAddPublicKey((const char*)userToAddPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); const void *groupToAddUserToRaw = sqlite3_column_blob(selectNodeAddUserDataStmt, 1); u8 groupToAddUserTo[GROUP_ID_LENGTH]; memcpy(groupToAddUserTo, groupToAddUserToRaw, GROUP_ID_LENGTH); bool appendObjectResult = decryptNodeAddUser(rowId, nodeHash, requestHash, timestamp, &creatorPublicKey, &userToAddPublicKey, DataView(groupToAddUserTo, GROUP_ID_LENGTH), decryptionKey); if(!appendObjectResult) success = false; } } else if(rc == SQLITE_DONE) { break; } else { string errMsg = "select NodeAddData by node failed with error: "; errMsg += sqlite3_errmsg(sqliteDb); throw DatabaseStorageException(errMsg); } } transaction.commit(); return success; } void DatabaseStorage::setNodeAddDataDecrypted(i64 rowId) { sqlite3_reset(setNodeAddDataDecryptedStmt); sqlite3_clear_bindings(setNodeAddDataDecryptedStmt); int rc; rc = sqlite3_bind_int(setNodeAddDataDecryptedStmt, 1, 1); bindCheckError(rc); rc = sqlite3_bind_int64(setNodeAddDataDecryptedStmt, 2, rowId); bindCheckError(rc); sqlite_step_throw_on_failure(sqliteDb, setNodeAddDataDecryptedStmt, "set NodeAddData decrypted"); } void DatabaseStorage::setNodeAddDataDecryptedData(i64 rowId, const DataView &decryptedData) { setNodeAddDataDecrypted(rowId); sqlite3_reset(setNodeAddDataAdditionalDataStmt); sqlite3_clear_bindings(setNodeAddDataAdditionalDataStmt); int rc; rc = sqlite3_bind_blob(setNodeAddDataAdditionalDataStmt, 1, decryptedData.data, decryptedData.size, SQLITE_STATIC); bindCheckError(rc); rc = sqlite3_bind_int64(setNodeAddDataAdditionalDataStmt, 2, rowId); bindCheckError(rc); sqlite_step_throw_on_failure(sqliteDb, setNodeAddDataAdditionalDataStmt, "set NodeAddData decrypted"); } bool DatabaseStorage::decryptNodeAddData(i64 rowId, const Hash &nodeHash, const Hash &dataHash, u64 timestamp, const Signature::PublicKey *creatorPublicKey, const DataView &encryptedData, const shared_ptr decryptionKey) { if(!isUserAllowedToAddDataInNode(nodeHash, *creatorPublicKey)) { // TODO: User might have permission to perform operation, but we haven't got the packet that adds user to the group with the permission, // or we haven't received the packet that modifies group with the permission to perform the operation. // This also means that an user can be in a group that has permission to perform the operation and then later be removed from it, // and remote peers would accept our request to perform operation if they haven't received the operation that removes the user from the group. // How to handle this? Log::error("User %s is not allowed to add data to node %s", creatorPublicKey->toString().c_str(), nodeHash.toString().c_str()); return false; } sibs::SafeDeserializer deserializer((const u8*)encryptedData.data, encryptedData.size); u8 nonce[ENCRYPTION_NONCE_BYTE_SIZE]; deserializer.extract(nonce, ENCRYPTION_NONCE_BYTE_SIZE); DataView dataToDecrypt((void*)deserializer.getBuffer(), deserializer.getSize()); try { Decryption decryptedBody(dataToDecrypt, DataView(nonce, ENCRYPTION_NONCE_BYTE_SIZE), DataView(decryptionKey->data, ENCRYPTION_KEY_BYTE_SIZE)); setNodeAddDataDecryptedData(rowId, decryptedBody.getDecryptedText()); Log::debug("Got add object, timestamp: %zu, data: %.*s", timestamp, decryptedBody.getDecryptedText().size, decryptedBody.getDecryptedText().data); const DatabaseAddNodeRequest addNodeRequest(&nodeHash, &dataHash, timestamp, creatorPublicKey, decryptedBody.getDecryptedText(), database); if(database->onAddNodeCallbackFunc) { std::lock_guard lock(databaseOperationCallbackMutex); database->onAddNodeCallbackFunc(addNodeRequest); } } catch(DecryptionException &e) { Log::error("Failed to decrypt data. Nonce: (data: %s, size: %u), dataToDecrypt: (data: %s, size: %u)", bin2hex((const char*)nonce, ENCRYPTION_NONCE_BYTE_SIZE).c_str(), ENCRYPTION_NONCE_BYTE_SIZE, bin2hex((const char*)dataToDecrypt.data, dataToDecrypt.size).c_str(), dataToDecrypt.size); throw; } return true; } bool DatabaseStorage::decryptNodeAddUser(i64 rowId, const Hash &nodeHash, const Hash &dataHash, u64 timestamp, const Signature::PublicKey *creatorPublicKey, const Signature::PublicKey *userToAddPublicKey, const DataView &groupToAddUserTo, const shared_ptr decryptionKey) { if(!isUserAllowedToAddUserToGroupInNode(nodeHash, *creatorPublicKey, groupToAddUserTo)) { // TODO: User might have permission to perform operation, but we haven't got the packet that adds user to the group with the permission, // or we haven't received the packet that modifies group with the permission to perform the operation. // This also means that an user can be in a group that has permission to perform the operation and then later be removed from it, // and remote peers would accept our request to perform operation if they haven't received the operation that removes the user from the group. // How to handle this? string errMsg = "User "; errMsg += creatorPublicKey->toString(); errMsg += " is not allowed to perform the operation: ADD USER"; throw PermissionDeniedException(errMsg); } addUser(nodeHash, *userToAddPublicKey, groupToAddUserTo); setNodeAddDataDecrypted(rowId); Log::debug("Got add user object, timestamp: %zu, user added: %s", timestamp, userToAddPublicKey->toString().c_str()); DatabaseAddUserRequest addUserRequest(&nodeHash, &dataHash, timestamp, creatorPublicKey, userToAddPublicKey, groupToAddUserTo, database); if(database->onAddUserCallbackFunc) { std::lock_guard lock(databaseOperationCallbackMutex); database->onAddUserCallbackFunc(addUserRequest); } return true; } void DatabaseStorage::update() { auto time = chrono::high_resolution_clock::now().time_since_epoch(); auto timeMicroseconds = chrono::duration_cast(time).count(); } int DatabaseStorage::clearCache() { return sqlite3_db_release_memory(sqliteDb); } }