diff options
-rw-r--r-- | include/odhtdb/Database.hpp | 9 | ||||
-rw-r--r-- | include/odhtdb/DatabaseStorage.hpp | 27 | ||||
-rw-r--r-- | include/odhtdb/Signature.hpp | 3 | ||||
-rw-r--r-- | src/Database.cpp | 10 | ||||
-rw-r--r-- | src/DatabaseStorage.cpp | 113 | ||||
-rw-r--r-- | src/Signature.cpp | 10 | ||||
-rw-r--r-- | tests/main.cpp | 22 |
7 files changed, 130 insertions, 64 deletions
diff --git a/include/odhtdb/Database.hpp b/include/odhtdb/Database.hpp index 5ebe9c5..43c1ad9 100644 --- a/include/odhtdb/Database.hpp +++ b/include/odhtdb/Database.hpp @@ -175,6 +175,15 @@ namespace odhtdb void addUser(const DatabaseNode &nodeInfo, const Signature::KeyPair &userToPerformActionWith, const Signature::PublicKey &userToAddPublicKey, const DataView &groupToAddUserTo); ntp::NtpTimestamp getSyncedTimestampUtc() const; + + // Username has to be either unique or if it's the same as existing one, then password has to match. + // Node has to be unique for the user. + // Throws DatabaseStorageWrongPassword or SqlExecException on failure (if username is not unique in node). + void storeUserPasswordEncrypted(const Hash &nodeHash, const std::string &username, const std::string &password, const Signature::KeyPair &keyPair); + + // Returns nodes, public key and private key of encrypted user. + // Throws DatabaseStorageWrongPassword if password for the stored user is wrong. + std::vector<NodeUserKeyPair> getStoredUserNodeDataDecrypted(const std::string &username, const std::string &password); private: void deserializeCreateRequest(const std::shared_ptr<dht::Value> &value, const Hash &hash, const std::shared_ptr<OwnedMemory> encryptionKey); void deserializeAddRequest(const std::shared_ptr<dht::Value> &value, const Hash &requestDataHash, const std::shared_ptr<Hash> &nodeHash, const std::shared_ptr<OwnedMemory> encryptionKey); diff --git a/include/odhtdb/DatabaseStorage.hpp b/include/odhtdb/DatabaseStorage.hpp index 886412c..613f2e8 100644 --- a/include/odhtdb/DatabaseStorage.hpp +++ b/include/odhtdb/DatabaseStorage.hpp @@ -10,6 +10,8 @@ #include "OwnedMemory.hpp" #include "DatabaseOperation.hpp" #include "DatabaseOrder.hpp" +#include "sql/SqlQuery.hpp" +#include "sql/SqlExec.hpp" #include <vector> #include <stdexcept> #include <boost/filesystem/path.hpp> @@ -50,12 +52,6 @@ namespace odhtdb DatabaseStorageCorrupt(const std::string &errMsg) : DatabaseStorageException(errMsg) {} }; - class DatabaseStorageNoSuchLocalStorageUser : public DatabaseStorageException - { - public: - DatabaseStorageNoSuchLocalStorageUser(const std::string &errMsg) : DatabaseStorageException(errMsg) {} - }; - class DatabaseStorageWrongPassword : public DatabaseStorageException { public: @@ -70,6 +66,12 @@ namespace odhtdb using FetchNodeUserActionGapsCallbackFunc = std::function<void(const DataView userPublicKey, u64 start, u64 range)>; using FetchNodeUserLatestActionCounterCallbackFunc = std::function<void(const DataView userPublicKey, u64 latestActionCounter)>; + struct NodeUserKeyPair + { + const Hash nodeHash; + const Signature::KeyPair keyPair; + }; + class DatabaseStorage { public: @@ -109,13 +111,14 @@ namespace odhtdb // Throws DatabaseStorageNotFound if user doesn't exist in node u64 getUserActionCounter(const Hash &nodeHash, const Signature::PublicKey &userPublicKey) const; - // Username and key pair has to be unique, returns true on success - //bool storeLocalUser(const std::string &username, const Signature::KeyPair &keyPair, const std::string &password); + // Username has to be either unique or if it's the same as existing one, then password has to match. + // Node has to be unique for the user. + // Throws DatabaseStorageWrongPassword or SqlExecException on failure (if username is not unique in node). + void storeUserPasswordEncrypted(const Hash &nodeHash, const std::string &username, const std::string &password, const Signature::KeyPair &keyPair); - // Returns public key and private key of encrypted local user. - // Throws DatabaseStorageNoSuchLocalStorageUser if user does not exist in local storage. - // Throws DatabaseStorageWrongPassword if password for the stored local user is wrong. - //Signature::KeyPair decryptLocalEncryptedUser(const std::string &username, const std::string &password); + // Returns nodes, public key and private key of encrypted user. + // Throws DatabaseStorageWrongPassword if password for the stored user is wrong. + std::vector<NodeUserKeyPair> getStoredUserNodeDataDecrypted(const std::string &username, const std::string &password); // Returns true and node decryption key if node exists and we have the decryption key, // otherwise return false and OwnedMemory with data set to nullptr diff --git a/include/odhtdb/Signature.hpp b/include/odhtdb/Signature.hpp index 0fc9087..92042f4 100644 --- a/include/odhtdb/Signature.hpp +++ b/include/odhtdb/Signature.hpp @@ -106,6 +106,9 @@ namespace odhtdb const char* getData() const { return data; } size_t getSize() const { return PRIVATE_KEY_NUM_BYTES; } + bool operator==(const PrivateKey &other) const; + bool operator!=(const PrivateKey &other) const; + // Throws DataSignException if signing data failed for whatever reason. This wont happen unless there is an issue with the private key std::string sign(const DataView &dataToSign) const; std::string toString() const; diff --git a/src/Database.cpp b/src/Database.cpp index 5eedc7a..17773ed 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -654,4 +654,14 @@ namespace odhtdb } return true; } + + void Database::storeUserPasswordEncrypted(const Hash &nodeHash, const std::string &username, const std::string &password, const Signature::KeyPair &keyPair) + { + return databaseStorage.storeUserPasswordEncrypted(nodeHash, username, password, keyPair); + } + + vector<NodeUserKeyPair> Database::getStoredUserNodeDataDecrypted(const std::string &username, const std::string &password) + { + return databaseStorage.getStoredUserNodeDataDecrypted(username, password); + } } diff --git a/src/DatabaseStorage.cpp b/src/DatabaseStorage.cpp index d5053c5..66d6d89 100644 --- a/src/DatabaseStorage.cpp +++ b/src/DatabaseStorage.cpp @@ -5,8 +5,6 @@ #include "../include/odhtdb/PasswordHash.hpp" #include "../include/odhtdb/Log.hpp" #include "../include/odhtdb/Database.hpp" -#include "../include/odhtdb/sql/SqlQuery.hpp" -#include "../include/odhtdb/sql/SqlExec.hpp" #include <cstring> #include <chrono> #include <boost/filesystem/convenience.hpp> @@ -101,6 +99,8 @@ namespace odhtdb 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));" @@ -109,15 +109,17 @@ namespace odhtdb "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) REFERENCES Node(nodeHash), FOREIGN KEY(userPublicKey) REFERENCES NodeUser(publicKey), FOREIGN KEY(groupId) REFERENCES NodeGroup(groupId));" + "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 INTEGER NOT NULL, data BLOB NOT NULL, FOREIGN KEY(node) REFERENCES Node(id));" "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 NodeEncryptedUserData(node INTEGER NOT NULL, username BLOB NOT NULL, userPublicKey BLOB NOT NULL, nonce BLOB NOT NULL, userPrivateKeyEncrypted BLOB NOT NULL, 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 UniqueUserGroupAssoc ON NodeUserGroupAssoc(node, userPublicKey, groupId);" + "CREATE UNIQUE INDEX IF NOT EXISTS UniqueEncryptedUserDataInNode ON NodeEncryptedUserData(node, username);"); 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); @@ -781,7 +783,7 @@ namespace odhtdb 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()) + while(query.next()) { const DataView userPublicKey = query.getBlob(0); u64 latestActionCounter = query.getInt64(1); @@ -833,67 +835,74 @@ namespace odhtdb } return query.getInt64(0); } -#if 0 - bool DatabaseStorage::storeLocalUser(const string &username, const Signature::KeyPair &keyPair, const string &password) + + void DatabaseStorage::storeUserPasswordEncrypted(const Hash &nodeHash, const std::string &username, const std::string &password, const Signature::KeyPair &keyPair) { - auto it = nameLocalUsersMap.find(username); - if(it != nameLocalUsersMap.end()) - return false; - OwnedMemory hashedPassword = hashPassword(DataView((void*)password.data(), password.size()), DataView((void*)passwordSalt, PASSWORD_SALT_LEN)); - DataView privateKeyView((void*)keyPair.getPrivateKey().getData(), PRIVATE_KEY_NUM_BYTES); DataView hashedPasswordView(hashedPassword.data, hashedPassword.size); + DataView privateKeyView((void*)keyPair.getPrivateKey().getData(), PRIVATE_KEY_NUM_BYTES); Encryption encryptedPrivateKey(privateKeyView, {}, hashedPasswordView); - EncryptedPrivateKey userEncryptedPrivateKey; - memcpy(userEncryptedPrivateKey.nonce, encryptedPrivateKey.getNonce().data, ENCRYPTION_NONCE_BYTE_SIZE); - assert(sizeof(userEncryptedPrivateKey.encryptedPrivateKey) == encryptedPrivateKey.getCipherText().size); - memcpy(userEncryptedPrivateKey.encryptedPrivateKey, encryptedPrivateKey.getCipherText().data, encryptedPrivateKey.getCipherText().size); + SqlQuery query(sqliteDb, "SELECT nonce, userPrivateKeyEncrypted FROM NodeEncryptedUserData WHERE username = ?", + { DataView((void*)username.data(), username.size()) }); - LocalUserEncrypted *localUserEncrypted = LocalUserEncrypted::create(keyPair.getPublicKey(), userEncryptedPrivateKey); - nameLocalUsersMap[username] = localUserEncrypted; - - sibs::SafeSerializer serializer; - serializer.add((u8)username.size()); - serializer.add((const u8*)username.data(), username.size()); - serializer.add((const u8*)keyPair.getPublicKey().getData(), PUBLIC_KEY_NUM_BYTES); - serializer.add((const u8*)encryptedPrivateKey.getNonce().data, ENCRYPTION_NONCE_BYTE_SIZE); - serializer.add((const u8*)encryptedPrivateKey.getCipherText().data, ENCRYPTION_CHECKSUM_BYTE_SIZE + PRIVATE_KEY_NUM_BYTES); + if(query.next()) + { + const DataView storedNonce = query.getBlob(0); + const DataView storedPrivateKeyEncrypted = query.getBlob(1); + try + { + Decryption decryptedStoredPrivateKey(storedPrivateKeyEncrypted, storedNonce, hashedPasswordView); + } + catch(DecryptionException &e) + { + throw DatabaseStorageWrongPassword(e.what()); + } + } - fileAppend(localUsersFilePath, DataView(serializer.getBuffer().data(), serializer.getBuffer().size())); - return true; + SqlExec sqlExec(sqliteDb, "INSERT INTO NodeEncryptedUserData(node, username, userPublicKey, nonce, userPrivateKeyEncrypted) VALUES(?, ?, ?, ?, ?)"); + sqlExec.execWithArgs({ + DataView(nodeHash.getData(), nodeHash.getSize()), + DataView((void*)username.data(), username.size()), + DataView((void*)keyPair.getPublicKey().getData(), PUBLIC_KEY_NUM_BYTES), + encryptedPrivateKey.getNonce(), + encryptedPrivateKey.getCipherText() + }); } - Signature::KeyPair DatabaseStorage::decryptLocalEncryptedUser(const string &username, const string &password) + vector<NodeUserKeyPair> DatabaseStorage::getStoredUserNodeDataDecrypted(const std::string &username, const std::string &password) { - auto localUserIt = nameLocalUsersMap.find(username); - if(localUserIt == nameLocalUsersMap.end()) - { - string errMsg = "User "; - errMsg += username; - errMsg += " does not exist in local storage"; - throw DatabaseStorageNoSuchLocalStorageUser(errMsg); - } + OwnedMemory hashedPassword = hashPassword(DataView((void*)password.data(), password.size()), DataView((void*)passwordSalt, PASSWORD_SALT_LEN)); + DataView hashedPasswordView(hashedPassword.data, hashedPassword.size); - DataView passwordView((void*)password.data(), password.size()); - DataView saltView((void*)passwordSalt, PASSWORD_SALT_LEN); - try - { - auto privateKey = localUserIt->second->getPrivateKey().decrypt(passwordView, saltView); - Signature::KeyPair keyPair(localUserIt->second->getPublicKey(), privateKey); - return keyPair; - } - catch(DecryptionException &e) + vector<NodeUserKeyPair> result; + SqlQuery query(sqliteDb, "SELECT node, userPublicKey, nonce, userPrivateKeyEncrypted FROM NodeEncryptedUserData WHERE username = ?", { DataView((void*)username.data(), username.size()) }); + while(query.next()) { - string errMsg = "Wrong password provided for user "; - errMsg += username; - errMsg += " in local storage ("; - errMsg += e.what(); - errMsg += ")"; - throw DatabaseStorageWrongPassword(errMsg); + 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 storedPrivateKeyEncrypted = 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 decryptedStoredPrivateKey(storedPrivateKeyEncrypted, storedNonce, hashedPasswordView); + Signature::PrivateKey userPrivateKey((const char*)decryptedStoredPrivateKey.getDecryptedText().data, decryptedStoredPrivateKey.getDecryptedText().size); + Signature::KeyPair keyPair(userPublicKey, userPrivateKey); + result.push_back({ nodeHash, keyPair }); + } + catch(DecryptionException &e) + { + throw DatabaseStorageWrongPassword(e.what()); + } } + return result; } -#endif + pair<bool, shared_ptr<OwnedMemory>> DatabaseStorage::getNodeDecryptionKey(const Hash &nodeHash) { sqlite3_reset(getNodeDecryptionKeyStmt); diff --git a/src/Signature.cpp b/src/Signature.cpp index ba3ebde..3d75f24 100644 --- a/src/Signature.cpp +++ b/src/Signature.cpp @@ -105,6 +105,16 @@ namespace odhtdb return *this; } + bool PrivateKey::operator==(const PrivateKey &other) const + { + return memcmp(data, other.data, PRIVATE_KEY_NUM_BYTES) == 0; + } + + bool PrivateKey::operator!=(const PrivateKey &other) const + { + return !operator==(other); + } + string PrivateKey::sign(const DataView &dataToSign) const { string result; diff --git a/tests/main.cpp b/tests/main.cpp index 07e7131..d2e49f9 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -199,6 +199,28 @@ int main() assertEquals(1, createNodeCounter); assertEquals(2, addDataCounter); assertEquals(1, addUserCounter); + + string username = "dec05eba"; + string password = "secretPassword"; + database.storeUserPasswordEncrypted(*databaseNode.getRequestHash(), username, password, *adminUserKey); + try + { + database.storeUserPasswordEncrypted(*databaseNode.getRequestHash(), username, password, localUserKeyPair); + fail("Expected store user password to fail since we have already stored an user in the node"); + } + catch(SqlExecException &e) + { + Log::debug("Failed with sql exception as expected, since we already have an user in the node: %s", e.what()); + } + + auto nodeUserData = database.getStoredUserNodeDataDecrypted(username, password); + assertEquals((size_t)1, nodeUserData.size()); + if(nodeUserData[0].nodeHash != *databaseNode.getRequestHash()) + fail("Expected stored node hash to match node hash"); + if(nodeUserData[0].keyPair.getPublicKey() != adminUserKey->getPublicKey()) + fail("Expected stored public key to match admin user public key"); + if(nodeUserData[0].keyPair.getPrivateKey() != adminUserKey->getPrivateKey()) + fail("Expected stored private key to match admin user private key"); } Log::debug("Callback works when adding data while connected, now testing to reconnect and check if data remains..."); { |