diff options
33 files changed, 1023 insertions, 134 deletions
@@ -1,3 +1,4 @@ sibs-build/ .kdev4/ odhtdb.kdev4 +storage/ @@ -30,3 +30,10 @@ This can happen because we dont verify all peers get the data so by the time one Operations are parsed in order for the receiving peer using operation timestamp. ## Padding Packets should have padding in the encrypted data to prevent peers without encryption key to analyze content +## Fake packets +To prevent duplication of packets, the hash of packets should be stored and each request should check against the hash list if it has not already been received +## Functionality for dependant applications +This database is used for chat application, and in chat applications you may want to have the ability to invite users to a channel using an invite link. +Functionality for an invite link that is only available for a certain amount of time can be added by generating an invite packet as an admin user with timestamp +and the user that should be added can be excluded from the signed packet, allowing any user to be added to channel while the invite link is valid. +The invite link could be converted to hex string to make it shareable and also generate QR-code using it to make it easy to join with mobile device. diff --git a/include/odhtdb/Database.hpp b/include/odhtdb/Database.hpp index 926437a..a8833fc 100644 --- a/include/odhtdb/Database.hpp +++ b/include/odhtdb/Database.hpp @@ -10,6 +10,11 @@ #include "Signature.hpp" #include "Permission.hpp" #include "DatabaseNode.hpp" +#include "Encryption.hpp" +#include "OwnedMemory.hpp" +#ifdef DEBUG +#undef DEBUG +#endif #include <opendht/dhtrunner.h> #include <vector> #include <ntp/NtpClient.hpp> @@ -117,31 +122,31 @@ namespace odhtdb class DatabaseCreateResponse { public: - DatabaseCreateResponse(LocalUser *nodeAdminUser, const std::shared_ptr<char*> &key, const std::shared_ptr<Hash> &hash); + DatabaseCreateResponse(LocalUser *nodeAdminUser, const std::shared_ptr<OwnedMemory> &key, const std::shared_ptr<Hash> &hash); const LocalUser* getNodeAdminUser() const; // Size of encryption key is odhtdb::KEY_BYTE_SIZE (found in Encryption.hpp) - const std::shared_ptr<char*> getNodeEncryptionKey() const; + const std::shared_ptr<OwnedMemory> getNodeEncryptionKey() const; const std::shared_ptr<Hash> getRequestHash() const; private: LocalUser *nodeAdminUser; - std::shared_ptr<char*> key; + std::shared_ptr<OwnedMemory> key; std::shared_ptr<Hash> hash; }; class Database { public: - Database(const char *bootstrapNodeAddr, u16 port, boost::filesystem::path storageDir); + Database(const char *bootstrapNodeAddr, u16 port, const boost::filesystem::path &storageDir); ~Database(); - void seed(const std::shared_ptr<Hash> hash, const std::shared_ptr<char*> encryptionKey); + void seed(const DatabaseNode &nodeToSeed); // Throws DatabaseCreateException on failure. - std::unique_ptr<DatabaseCreateResponse> create(const std::string &ownerName, const std::string &nodeName); + std::unique_ptr<DatabaseCreateResponse> create(const std::string &ownerName, const std::string &ownerPlainPassword, const std::string &nodeName); // Throws DatabaseAddException on failure void addData(const DatabaseNode &nodeInfo, LocalUser *userToPerformActionWith, DataView dataToAdd); // Throws PermissionDeniedException if user @userToPerformActionWith is not allowed to add user @userToAdd to group @groupToAddUserTo - void addUserToGroup(const DatabaseNode &nodeInfo, LocalUser *userToPerformActionWith, const std::string &userToAddName, const Signature::PublicKey &userToAddPublicKey, Group *groupToAddUserTo); + void addUser(const DatabaseNode &nodeInfo, LocalUser *userToPerformActionWith, const std::string &userToAddName, const Signature::PublicKey &userToAddPublicKey, Group *groupToAddUserTo); void commit(); void setOnCreateNodeCallback(std::function<void(const DatabaseCreateNodeRequest&)> callbackFunc); @@ -155,10 +160,10 @@ namespace odhtdb // Throws CommitAddException on failure void commitStagedAddObject(const std::unique_ptr<StagedObject> &stagedObject); ntp::NtpTimestamp getSyncedTimestampUtc() const; - void deserializeCreateRequest(const std::shared_ptr<dht::Value> &value, const Hash &hash, const std::shared_ptr<char*> encryptionKey); - void deserializeAddRequest(const std::shared_ptr<dht::Value> &value, const Hash &requestDataHash, const std::shared_ptr<char*> encryptionKey); - bool listenCreateData(std::shared_ptr<dht::Value> value, const Hash &hash, const std::shared_ptr<char*> encryptionKey); - bool listenAddData(std::shared_ptr<dht::Value> value, const Hash &requestDataHash, const std::shared_ptr<char*> encryptionKey); + 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); + bool listenCreateData(std::shared_ptr<dht::Value> value, const Hash &hash, const std::shared_ptr<OwnedMemory> encryptionKey); + bool listenAddData(std::shared_ptr<dht::Value> value, const Hash &requestDataHash, const std::shared_ptr<Hash> nodeHash, const std::shared_ptr<OwnedMemory> encryptionKey); private: dht::DhtRunner node; std::vector<std::unique_ptr<StagedObject>> stagedCreateObjects; diff --git a/include/odhtdb/DatabaseNode.hpp b/include/odhtdb/DatabaseNode.hpp index 3ca4be3..620cd40 100644 --- a/include/odhtdb/DatabaseNode.hpp +++ b/include/odhtdb/DatabaseNode.hpp @@ -1,6 +1,7 @@ #pragma once #include "Hash.hpp" +#include "OwnedMemory.hpp" #include <memory> namespace odhtdb @@ -8,14 +9,16 @@ namespace odhtdb class DatabaseNode { public: - DatabaseNode(const std::shared_ptr<char*> &_encryptionKey, const std::shared_ptr<Hash> &_nodeHash) : + DatabaseNode() {} + + DatabaseNode(const std::shared_ptr<OwnedMemory> &_encryptionKey, const std::shared_ptr<Hash> &_nodeHash) : encryptionKey(_encryptionKey), nodeHash(_nodeHash) { } - const std::shared_ptr<char*> getNodeEncryptionKey() const + const std::shared_ptr<OwnedMemory> getNodeEncryptionKey() const { return encryptionKey; } @@ -25,7 +28,7 @@ namespace odhtdb return nodeHash; } private: - std::shared_ptr<char*> encryptionKey; + std::shared_ptr<OwnedMemory> encryptionKey; std::shared_ptr<Hash> nodeHash; }; } diff --git a/include/odhtdb/DatabaseStorage.hpp b/include/odhtdb/DatabaseStorage.hpp index ad4f70b..a2789f7 100644 --- a/include/odhtdb/DatabaseStorage.hpp +++ b/include/odhtdb/DatabaseStorage.hpp @@ -5,12 +5,14 @@ #include "DataView.hpp" #include "Signature.hpp" #include "Encryption.hpp" +#include "Group.hpp" #include <vector> #include <stdexcept> +#include <boost/filesystem/path.hpp> +#include <sibs/SafeDeserializer.hpp> namespace odhtdb { - class Group; class User; struct DatabaseStorageObject @@ -52,12 +54,30 @@ namespace odhtdb DatabaseStorageNotFound(const std::string &errMsg) : std::runtime_error(errMsg) {} }; + class DatabaseStorageCorrupt : public std::runtime_error + { + public: + DatabaseStorageCorrupt(const std::string &errMsg) : std::runtime_error(errMsg) {} + }; + using DatabaseStorageMap = MapHash<DatabaseStorageObjectList*>; using DatabaseStorageQuarantineMap = Signature::MapPublicKey<std::vector<DatabaseStorageQuarantineObject*>>; + const int PASSWORD_SALT_LEN = 16; + const int HASHED_PASSWORD_LEN = 32; + class DatabaseStorage { public: + struct UserData + { + User *user; + u8 hashedPassword[HASHED_PASSWORD_LEN]; // All bytes are zero if user is not local + }; + + // Throws DatabaseStorageCorrupt if storage is corrupted + DatabaseStorage(const boost::filesystem::path &storagePath); + // Throws DatabaseStorageAlreadyExists if data with hash already exists void createStorage(const Hash &hash, Group *creatorGroup, u64 timestamp, const u8 *data, usize dataSize); @@ -68,28 +88,43 @@ namespace odhtdb // Throws DatabaseStorageAlreadyExists if same data has been added before (hash of @data, in @dataHash) void addToQuarantine(const Hash &dataHash, const Signature::PublicKey &creatorPublicKey, u64 timestamp, const u8 *data, usize dataSize); - void addUser(User *user, const Hash &hash); + // Return false if group with id already exists, otherwise return true + bool addGroup(const Hash &nodeHash, Group *group); + + // Return false if user public key already exists, otherwise return true + bool addUser(const Hash &nodeHash, User *user); // Returns nullptr if no storage with provided hash exists const DatabaseStorageObjectList* getStorage(const Hash &hash) const; - // Returns nullptr if no node with the user exists - const Hash* getNodeByUserPublicKey(const Signature::PublicKey &userPublicKey) const; + // Returns nullptr if a group with id @groupId doesn't exist in node @nodeHash or if no node with id @nodeHash exists + Group* getGroupById(const Hash &nodeHash, uint8_t groupId[GROUP_ID_LENGTH]); - // Returns nullptr if no user with public key exists - const User* getUserByPublicKey(const Signature::PublicKey &userPublicKey) const; + // Returns nullptr if a user with public key @publicKey doesn't exist in node @nodeHash or if no node with id @nodeHash exists + User* getUserByPublicKey(const Hash &nodeHash, Signature::PublicKey &userPublicKey); - // Returns nullptr if a group with id @groupId doesn't exist - Group* getGroupById(uint8_t groupId[16]); + // Return users in node, or nullptr if no node with id @nodeHash exists + const Signature::MapPublicKey<UserData*>* getUsersData(const Hash &nodeHash) const; // Update storage state (remove quarantine objects if they are too old, etc) void update(); private: + void loadGroupsFromFile(); + void loadUsersFromFile(); + void loadDataFromFile(); + void loadMetadataFromFile(); + void loadStorageCreate(sibs::SafeDeserializer &deserializer); + void loadStorageAppend(sibs::SafeDeserializer &deserializer); + private: DatabaseStorageMap storageMap; DatabaseStorageQuarantineMap quarantineStorageMap; SetHash storedDataHash; // Prevent duplicate data from being added - Signature::MapPublicKey<Hash*> userPublicKeyNodeMap; - Signature::MapPublicKey<const User*> publicKeyUserMap; - DataViewMap<Group*> groupByIdMap; + MapHash<Signature::MapPublicKey<UserData*>*> nodePublicKeyUserDataMap; + MapHash<DataViewMap<Group*>*> nodeGroupByIdMap; + boost::filesystem::path groupsFilePath; + boost::filesystem::path usersFilePath; + boost::filesystem::path dataFilePath; + boost::filesystem::path metadataFilePath; + u8 passwordSalt[PASSWORD_SALT_LEN]; }; } diff --git a/include/odhtdb/Encryption.hpp b/include/odhtdb/Encryption.hpp index 4697b35..5271cbd 100644 --- a/include/odhtdb/Encryption.hpp +++ b/include/odhtdb/Encryption.hpp @@ -11,8 +11,8 @@ namespace odhtdb { - const int NONCE_BYTE_SIZE = 24; - const int KEY_BYTE_SIZE = 32; + const int ENCRYPTION_NONCE_BYTE_SIZE = 24; + const int ENCRYPTION_KEY_BYTE_SIZE = 32; class EncryptionException : public std::runtime_error { @@ -38,8 +38,8 @@ namespace odhtdb DataView getNonce() const; DataView getCipherText() const; private: - unsigned char key[KEY_BYTE_SIZE]; - unsigned char nonce[NONCE_BYTE_SIZE]; + unsigned char key[ENCRYPTION_KEY_BYTE_SIZE]; + unsigned char nonce[ENCRYPTION_NONCE_BYTE_SIZE]; unsigned char *cipherText; unsigned long long cipherTextLength; }; diff --git a/include/odhtdb/FileUtils.hpp b/include/odhtdb/FileUtils.hpp new file mode 100644 index 0000000..7bfbe3e --- /dev/null +++ b/include/odhtdb/FileUtils.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include <boost/filesystem/path.hpp> +#include <stdexcept> +#include "OwnedMemory.hpp" +#include "DataView.hpp" + +namespace odhtdb +{ + class FileException : public std::runtime_error + { + public: + FileException(const std::string &errMsg) : std::runtime_error(errMsg) {} + }; + + // Throws FileException on error + OwnedMemory fileGetContent(const boost::filesystem::path &filepath); + + // Creates file if it doesn't exist. + // Throws FileException on error + void fileAppend(const boost::filesystem::path &filepath, const DataView &data); +} diff --git a/include/odhtdb/Group.hpp b/include/odhtdb/Group.hpp index 315961d..890b2fc 100644 --- a/include/odhtdb/Group.hpp +++ b/include/odhtdb/Group.hpp @@ -21,13 +21,13 @@ namespace odhtdb } }; - + const int GROUP_ID_LENGTH = 16; class Group { friend class User; public: - Group(const std::string &name, uint8_t id[16], const Permission &permission); + Group(const std::string &name, uint8_t id[GROUP_ID_LENGTH], const Permission &permission); ~Group(); const std::string& getName() const; @@ -38,7 +38,7 @@ namespace odhtdb void addUser(const User *user); private: std::string name; - uint8_t id[16]; + uint8_t id[GROUP_ID_LENGTH]; Permission permission; std::vector<const User*> users; }; diff --git a/include/odhtdb/LocalUser.hpp b/include/odhtdb/LocalUser.hpp index d60cb38..0312a38 100644 --- a/include/odhtdb/LocalUser.hpp +++ b/include/odhtdb/LocalUser.hpp @@ -1,15 +1,16 @@ #pragma once #include "User.hpp" +#include "types.hpp" namespace odhtdb { class LocalUser : public User { public: - static LocalUser* create(const Signature::KeyPair &keyPair, const std::string &name, Group *group) + static LocalUser* create(const Signature::KeyPair &keyPair, const std::string &name, Group *group, const std::string &plainPassword) { - return new LocalUser(keyPair, name, group); + return new LocalUser(keyPair, name, group, plainPassword); } const Signature::PublicKey& getPublicKey() const override @@ -21,9 +22,15 @@ namespace odhtdb { return keyPair.getPrivateKey(); } + + const std::string& getPlainPassword() const + { + return plainPassword; + } private: - LocalUser(const Signature::KeyPair &_keyPair, const std::string &name, Group *group) : User(name, group), keyPair(_keyPair) {} + LocalUser(const Signature::KeyPair &_keyPair, const std::string &name, Group *group, const std::string &plainPassword); private: Signature::KeyPair keyPair; + std::string plainPassword; }; } diff --git a/include/odhtdb/LocalUserEncrypted.hpp b/include/odhtdb/LocalUserEncrypted.hpp new file mode 100644 index 0000000..c250d13 --- /dev/null +++ b/include/odhtdb/LocalUserEncrypted.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include "User.hpp" +#include "types.hpp" +#include "Encryption.hpp" + +namespace odhtdb +{ + struct EncryptedPrivateKey + { + u8 nonce[ENCRYPTION_NONCE_BYTE_SIZE]; + u8 encryptedPrivateKey[16 + PRIVATE_KEY_NUM_BYTES]; + + EncryptedPrivateKey(); + EncryptedPrivateKey(const EncryptedPrivateKey &other); + + // Throws DecryptionException if password (or salt) is wrong + Signature::PrivateKey decrypt(const DataView &plainPassword, const DataView &salt) const; + }; + + // Local user with encrypted private key + class LocalUserEncrypted : public User + { + public: + static LocalUserEncrypted* create(const Signature::PublicKey &publicKey, const EncryptedPrivateKey &encryptedPrivateKey, const std::string &name, Group *group) + { + return new LocalUserEncrypted(publicKey, encryptedPrivateKey, name, group); + } + + const Signature::PublicKey& getPublicKey() const override + { + return publicKey; + } + + const EncryptedPrivateKey& getPrivateKey() const + { + return encryptedPrivateKey; + } + private: + LocalUserEncrypted(const Signature::PublicKey &_publicKey, const EncryptedPrivateKey &_encryptedPrivateKey, const std::string &name, Group *group) : + User(User::Type::LOCAL_ENCRYPTED, name, group), + publicKey(_publicKey), + encryptedPrivateKey(_encryptedPrivateKey) + { + + } + private: + Signature::PublicKey publicKey; + EncryptedPrivateKey encryptedPrivateKey; + }; +} diff --git a/include/odhtdb/OwnedMemory.hpp b/include/odhtdb/OwnedMemory.hpp new file mode 100644 index 0000000..5dcdf25 --- /dev/null +++ b/include/odhtdb/OwnedMemory.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "types.hpp" + +namespace odhtdb +{ + class OwnedMemory + { + public: + OwnedMemory() : data(nullptr), size(0) {} + OwnedMemory(void *_data, usize _size) : data(_data), size(_size) {} + OwnedMemory(OwnedMemory &&other); + ~OwnedMemory(); + + // Do not allow copy of this struct, forcing move when returning a OwnedMemory in a function + OwnedMemory(OwnedMemory&) = delete; + OwnedMemory& operator = (OwnedMemory&) = delete; + + void *data; + usize size; + }; +} diff --git a/include/odhtdb/PasswordHash.hpp b/include/odhtdb/PasswordHash.hpp new file mode 100644 index 0000000..08b1857 --- /dev/null +++ b/include/odhtdb/PasswordHash.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include "OwnedMemory.hpp" +#include "DataView.hpp" + +namespace odhtdb +{ + OwnedMemory hashPassword(const DataView &plainPassword, const DataView &salt); +} diff --git a/include/odhtdb/Permission.hpp b/include/odhtdb/Permission.hpp index 1ae2642..1e7bb0c 100644 --- a/include/odhtdb/Permission.hpp +++ b/include/odhtdb/Permission.hpp @@ -31,6 +31,9 @@ namespace odhtdb { public: // @permissionLevel is hierarchical access right. A group can only modify a group that has higher @permissionLevel value + Permission(u8 permissionLevel, u32 permissionFlags); + + // @permissionLevel is hierarchical access right. A group can only modify a group that has higher @permissionLevel value Permission(u8 permissionLevel, std::initializer_list<PermissionType> permissions); u8 getPermissionLevel() const { return permissionLevel; } diff --git a/include/odhtdb/RemoteUser.hpp b/include/odhtdb/RemoteUser.hpp index 181a4c4..9dc8f96 100644 --- a/include/odhtdb/RemoteUser.hpp +++ b/include/odhtdb/RemoteUser.hpp @@ -17,7 +17,7 @@ namespace odhtdb return publicKey; } private: - RemoteUser(const Signature::PublicKey &_publicKey, const std::string &name, Group *group) : User(name, group), publicKey(_publicKey){} + RemoteUser(const Signature::PublicKey &_publicKey, const std::string &name, Group *group) : User(User::Type::REMOTE, name, group), publicKey(_publicKey){} private: Signature::PublicKey publicKey; }; diff --git a/include/odhtdb/Signature.hpp b/include/odhtdb/Signature.hpp index 62c41c3..db434ac 100644 --- a/include/odhtdb/Signature.hpp +++ b/include/odhtdb/Signature.hpp @@ -93,6 +93,8 @@ namespace odhtdb { friend class KeyPair; public: + static PrivateKey ZERO; + // Throws InvalidSignatureKeySize if size is not PRIVATE_KEY_NUM_BYTES PrivateKey(const char *data, size_t size); PrivateKey(const PrivateKey &other); @@ -113,9 +115,12 @@ namespace odhtdb class KeyPair { public: - // Throws SignatureGenerationException if generation of private/public key pair fails (should never happen) + // Generate a new key pair, Throws SignatureGenerationException if generation of private/public key pair fails (should never happen) KeyPair(); + // Create a key pair from existing public and private key + KeyPair(const PublicKey &publicKey, const PrivateKey &privateKey); + const PublicKey& getPublicKey() const { return publicKey; } const PrivateKey& getPrivateKey() const { return privateKey; } private: diff --git a/include/odhtdb/User.hpp b/include/odhtdb/User.hpp index fb37876..d6e551a 100644 --- a/include/odhtdb/User.hpp +++ b/include/odhtdb/User.hpp @@ -1,6 +1,7 @@ #pragma once #include "Signature.hpp" +#include "types.hpp" #include <string> #include <stdexcept> #include <vector> @@ -22,16 +23,25 @@ namespace odhtdb class User { public: + enum class Type : u8 + { + LOCAL, + LOCAL_ENCRYPTED, + REMOTE + }; + virtual ~User(){} void addToGroup(Group *group); + Type getType() const { return type; } const std::string& getName() const { return name; } const std::vector<Group*>& getGroups() const { return groups; } virtual const Signature::PublicKey& getPublicKey() const = 0; protected: - User(const std::string &name, Group *group); + User(Type type, const std::string &name, Group *group); private: + Type type; std::string name; std::vector<Group*> groups; }; diff --git a/include/odhtdb/env.hpp b/include/odhtdb/env.hpp new file mode 100644 index 0000000..bafc750 --- /dev/null +++ b/include/odhtdb/env.hpp @@ -0,0 +1,63 @@ +#pragma once + +#define OS_FAMILY_WINDOWS 0 +#define OS_FAMILY_POSIX 1 + +#define OS_TYPE_WINDOWS 0 +#define OS_TYPE_LINUX 1 + +#if defined(_WIN32) || defined(_WIN64) + #if defined(_WIN64) + #define SYS_ENV_64BIT + #else + #define SYS_ENV_32BIT + #endif + #define OS_FAMILY OS_FAMILY_WINDOWS + #define OS_TYPE OS_TYPE_WINDOWS + + #ifndef UNICODE + #define UNICODE + #endif + + #ifndef _UNICODE + #define _UNICODE + #endif + + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + + #include <Windows.h> +#endif + +#if defined(__linux__) || defined(__unix__) || defined(__APPLE__) || defined(_POSIX_VERSION) + #define OS_FAMILY OS_FAMILY_POSIX +#endif + +#ifdef __linux__ + #define OS_TYPE OS_TYPE_LINUX +#endif + +#if defined(__GNUC__) + #if defined(__x86_64__) || defined(__pc64__) + #define SYS_ENV_64BIT + #else + #define SYS_ENV_32BIT + #endif +#endif + +#if !defined(SYS_ENV_32BIT) && !defined(SYS_ENV_64BIT) + #error "System is not detected as either 32-bit or 64-bit" +#endif + +#if !defined(OS_FAMILY) + #error "System not supported. Only Windows and Posix systems supported right now" +#endif + +#if !defined(OS_TYPE) + #error "System not supported. Only Windows and linux systems supported right now" +#endif + +#if !defined(DEBUG) && !defined(NDEBUG) +#define DEBUG +#endif diff --git a/include/odhtdb/hex2bin.hpp b/include/odhtdb/hex2bin.hpp new file mode 100644 index 0000000..96a2229 --- /dev/null +++ b/include/odhtdb/hex2bin.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include <string> +#include <stdexcept> + +namespace odhtdb +{ + class InvalidHexStringFormat : public std::runtime_error + { + public: + InvalidHexStringFormat(const std::string &errMsg) : std::runtime_error(errMsg) {} + }; + + // Returns -1 if c is not a hex string (0-9a-fA-F) + static int __hexchar_to_num(int c) + { + if(c >= '0' && c <= '9') + return c - '0'; + else if(c >= 'a' && c <= 'f') + return 10 + (c - 'a'); + else if(c >= 'A' && c <= 'F') + return 10 + (c - 'A'); + else + return -1; + } + + // Throws InvalidHexStringFormat on invalid input + static std::string hex2bin(const char *data, size_t dataSize) + { + if(dataSize % 2 != 0) + throw InvalidHexStringFormat("Input string size is not of an even size"); + + std::string result; + result.resize(dataSize / 2); + + for(int i = 0; i < dataSize; i += 2) + { + int v = __hexchar_to_num(data[i]); + int v2 = __hexchar_to_num(data[i + 1]); + + if(v == -1) + { + std::string errMsg = "Invalid hex character: "; + errMsg += data[i]; + throw InvalidHexStringFormat(errMsg); + } + + if(v2 == -1) + { + std::string errMsg = "Invalid hex character: "; + errMsg += data[i + 1]; + throw InvalidHexStringFormat(errMsg); + } + + result[i / 2] = (v << 4) | v2; + } + + return result; + } +} diff --git a/project.conf b/project.conf index 93dfd0c..63336df 100644 --- a/project.conf +++ b/project.conf @@ -16,3 +16,4 @@ ntpclient = "0.2.0" sibs-serializer = "0.2.0" boost-filesystem = "1.66.0" boost-uuid = "1.66.0" +argon2 = "2017.12.27" diff --git a/src/Database.cpp b/src/Database.cpp index 41b0d73..226aa05 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -60,9 +60,9 @@ namespace odhtdb return DataView(result, allocationSize); } - DatabaseCreateResponse::DatabaseCreateResponse(LocalUser *_nodeAdminUser, const shared_ptr<char*> &_key, const shared_ptr<Hash> &_hash) : + DatabaseCreateResponse::DatabaseCreateResponse(LocalUser *_nodeAdminUser, const shared_ptr<OwnedMemory> &_key, const shared_ptr<Hash> &_hash) : nodeAdminUser(_nodeAdminUser), - key(move(_key)), + key(_key), hash(_hash) { @@ -73,7 +73,7 @@ namespace odhtdb return nodeAdminUser; } - const shared_ptr<char*> DatabaseCreateResponse::getNodeEncryptionKey() const + const shared_ptr<OwnedMemory> DatabaseCreateResponse::getNodeEncryptionKey() const { return key; } @@ -83,10 +83,11 @@ namespace odhtdb return hash; } - Database::Database(const char *bootstrapNodeAddr, u16 port, boost::filesystem::path storageDir) : + Database::Database(const char *bootstrapNodeAddr, u16 port, const boost::filesystem::path &storageDir) : onCreateNodeCallbackFunc(nullptr), onAddNodeCallbackFunc(nullptr), - onAddUserCallbackFunc(nullptr) + onAddUserCallbackFunc(nullptr), + databaseStorage(storageDir) { // TODO: Cache this in storage. It takes pretty long time to generate new identity auto identity = dht::crypto::generateIdentity(); @@ -147,43 +148,43 @@ namespace odhtdb node.join(); } - void Database::seed(const shared_ptr<Hash> hash, const shared_ptr<char*> encryptionKey) + void Database::seed(const DatabaseNode &nodeToSeed) { // TODO: Use cached files and seed those. If none exists, request new files to seed. // If nobody requests my cached files in a long time, request new files to seed and remove cached files // (only if there are plenty of other seeders for the cached files. This could also cause race issue // where all nodes with a cached file delete it at same time) - Log::debug("Seeding key: %s", hash->toString().c_str()); - DhtKey dhtKey(*hash); + Log::debug("Seeding key: %s", nodeToSeed.getRequestHash()->toString().c_str()); + DhtKey dhtKey(*nodeToSeed.getRequestHash()); - node.listen(dhtKey.getNewDataListenerKey(), [this, hash, encryptionKey](const shared_ptr<Value> &value) + node.listen(dhtKey.getNewDataListenerKey(), [this, nodeToSeed](const shared_ptr<Value> &value) { Log::debug("Seed: New data listener received data..."); const Hash requestHash(value->data.data(), value->data.size()); - if(requestHash == *hash) + if(requestHash == *nodeToSeed.getRequestHash()) return true; //return listenCreateData(value, requestHash, encryptionKey); else - return listenAddData(value, requestHash, encryptionKey); + return listenAddData(value, requestHash, nodeToSeed.getRequestHash(), nodeToSeed.getNodeEncryptionKey()); }); u8 responseKey[OPENDHT_INFOHASH_LEN]; randombytes_buf(responseKey, OPENDHT_INFOHASH_LEN); // TODO: If this response key is spammed, generate a new one. - node.listen(InfoHash(responseKey, OPENDHT_INFOHASH_LEN), [this, hash, encryptionKey](const shared_ptr<Value> &value) + node.listen(InfoHash(responseKey, OPENDHT_INFOHASH_LEN), [this, nodeToSeed](const shared_ptr<Value> &value) { const Hash requestHash(value->data.data(), value->data.size()); - if(requestHash == *hash) - return listenCreateData(value, requestHash, encryptionKey); + if(requestHash == *nodeToSeed.getRequestHash()) + return listenCreateData(value, requestHash, nodeToSeed.getNodeEncryptionKey()); else - return listenAddData(value, requestHash, encryptionKey); + return listenAddData(value, requestHash, nodeToSeed.getRequestHash(), nodeToSeed.getNodeEncryptionKey()); }); // TODO: Before listening on this key, we should check how many remote peers are also providing this data. // This is to prevent too many peers from responding to a request to get old data. - node.listen(dhtKey.getRequestOldDataKey(), [this, hash](const shared_ptr<Value> &value) + node.listen(dhtKey.getRequestOldDataKey(), [this, nodeToSeed](const shared_ptr<Value> &value) { Log::debug("Request: Got request to send old data"); try @@ -193,10 +194,10 @@ namespace odhtdb u8 requestResponseKey[OPENDHT_INFOHASH_LEN]; deserializer.extract(requestResponseKey, OPENDHT_INFOHASH_LEN); - auto requestedData = databaseStorage.getStorage(*hash); + auto requestedData = databaseStorage.getStorage(*nodeToSeed.getRequestHash()); if(!requestedData) { - Log::warn("No data found for hash %s, unable to serve peer", hash->toString().c_str()); + Log::warn("No data found for hash %s, unable to serve peer", nodeToSeed.getRequestHash()->toString().c_str()); return true; } @@ -229,7 +230,7 @@ namespace odhtdb }); sibs::SafeSerializer serializer; - serializer.add((u64)0); // Timestamp in microseconds, fetch data newer than this + serializer.add((u64)0); // Timestamp in microseconds, fetch data newer than this. // TODO: Get timestamp from database storage serializer.add(responseKey, OPENDHT_INFOHASH_LEN); node.put(dhtKey.getRequestOldDataKey(), Value(serializer.getBuffer().data(), serializer.getBuffer().size()), [](bool ok) { @@ -241,13 +242,13 @@ namespace odhtdb //node.listen(ADD_DATA_HASH, bind(&Database::listenAddData, this, _1)); } - unique_ptr<DatabaseCreateResponse> Database::create(const std::string &ownerName, const std::string &nodeName) + unique_ptr<DatabaseCreateResponse> Database::create(const string &ownerName, const std::string &ownerPlainPassword, const string &nodeName) { // TODO: Should this be declared static? is there any difference in behavior/performance? boost::uuids::random_generator uuidGen; auto adminGroupId = uuidGen(); auto adminGroup = new Group("administrator", adminGroupId.data, ADMIN_PERMISSION); - LocalUser *nodeAdminUser = LocalUser::create(Signature::KeyPair(), ownerName, adminGroup); + LocalUser *nodeAdminUser = LocalUser::create(Signature::KeyPair(), ownerName, adminGroup, ownerPlainPassword); // Header sibs::SafeSerializer serializer; @@ -281,10 +282,10 @@ namespace odhtdb stagedCreateObjects.emplace_back(make_unique<StagedObject>(requestData, hashRequestKey)); - assert(encryptedBody.getKey().size == KEY_BYTE_SIZE); - char *key = new char[encryptedBody.getKey().size]; - memcpy(key, encryptedBody.getKey().data, encryptedBody.getKey().size); - return make_unique<DatabaseCreateResponse>(nodeAdminUser, make_shared<char*>(key), hashRequestKey); + assert(encryptedBody.getKey().size == ENCRYPTION_KEY_BYTE_SIZE); + auto key = make_shared<OwnedMemory>(new char[encryptedBody.getKey().size], encryptedBody.getKey().size); + memcpy(key->data, encryptedBody.getKey().data, encryptedBody.getKey().size); + return make_unique<DatabaseCreateResponse>(nodeAdminUser, move(key), hashRequestKey); } catch (EncryptionException &e) { @@ -301,7 +302,7 @@ namespace odhtdb serializer.add(timestampMicroseconds); serializer.add(DatabaseOperation::ADD_DATA); - DataView encryptionKey(*nodeInfo.getNodeEncryptionKey(), KEY_BYTE_SIZE); + DataView encryptionKey(nodeInfo.getNodeEncryptionKey()->data, ENCRYPTION_KEY_BYTE_SIZE); Encryption encryptedBody(dataToAdd, DataView(), encryptionKey); DataView requestData = combine(serializer, encryptedBody); string signedRequestData = userToPerformActionWith->getPrivateKey().sign(requestData); @@ -334,7 +335,7 @@ namespace odhtdb return nullptr; } - void Database::addUserToGroup(const DatabaseNode &nodeInfo, LocalUser *userToPerformActionWith, const std::string &userToAddName, const Signature::PublicKey &userToAddPublicKey, Group *groupToAddUserTo) + void Database::addUser(const DatabaseNode &nodeInfo, LocalUser *userToPerformActionWith, const string &userToAddName, const Signature::PublicKey &userToAddPublicKey, Group *groupToAddUserTo) { auto groupWithAddUserRights = getGroupWithRightsToAddUserToGroup(userToPerformActionWith->getGroups(), groupToAddUserTo); if(!groupWithAddUserRights) @@ -365,7 +366,7 @@ namespace odhtdb Hash requestDataHash(stagedAddObject.data, stagedAddObject.size); databaseStorage.appendStorage(*nodeInfo.getRequestHash(), requestDataHash, userToPerformActionWith, timestampMicroseconds, (u8*)stagedAddObject.data, stagedAddObject.size); auto userToAdd = RemoteUser::create(userToAddPublicKey, userToAddName, groupToAddUserTo); - databaseStorage.addUser(userToAdd, *nodeInfo.getRequestHash()); + databaseStorage.addUser(*nodeInfo.getRequestHash(), userToAdd); DatabaseAddUserRequest addUserRequest(&*nodeInfo.getRequestHash(), &requestDataHash, timestampMicroseconds, userToPerformActionWith, userToAdd, groupToAddUserTo); if(onAddUserCallbackFunc) @@ -446,7 +447,7 @@ namespace odhtdb return timestamp; } - void Database::deserializeCreateRequest(const std::shared_ptr<dht::Value> &value, const Hash &hash, const shared_ptr<char*> encryptionKey) + void Database::deserializeCreateRequest(const shared_ptr<dht::Value> &value, const Hash &hash, const shared_ptr<OwnedMemory> encryptionKey) { sibs::SafeDeserializer deserializer(value->data.data(), value->data.size()); u16 packetStructureVersion = deserializer.extract<u16>(); @@ -472,7 +473,7 @@ namespace odhtdb uint8_t adminGroupId[16]; deserializer.extract(adminGroupId, 16); - if(deserializer.getSize() < NONCE_BYTE_SIZE) + if(deserializer.getSize() < ENCRYPTION_NONCE_BYTE_SIZE) throw sibs::DeserializeException("Unsigned encrypted body is too small (unable to extract nonce)"); auto adminGroup = new Group("administrator", adminGroupId, ADMIN_PERMISSION); @@ -480,11 +481,11 @@ namespace odhtdb auto creatorUser = RemoteUser::create(userPublicKey, "ENCRYPTED USER NAME", adminGroup); databaseStorage.createStorage(hash, adminGroup, creationDate, value->data.data(), value->data.size()); - u8 nonce[NONCE_BYTE_SIZE]; - deserializer.extract(nonce, NONCE_BYTE_SIZE); + u8 nonce[ENCRYPTION_NONCE_BYTE_SIZE]; + deserializer.extract(nonce, ENCRYPTION_NONCE_BYTE_SIZE); DataView dataToDecrypt((void*)deserializer.getBuffer(), deserializer.getSize()); - Decryption decryptedBody(dataToDecrypt, DataView(nonce, NONCE_BYTE_SIZE), DataView(*encryptionKey, KEY_BYTE_SIZE)); + Decryption decryptedBody(dataToDecrypt, DataView(nonce, ENCRYPTION_NONCE_BYTE_SIZE), DataView(encryptionKey->data, ENCRYPTION_KEY_BYTE_SIZE)); sibs::SafeDeserializer bodyDeserializer((const u8*)decryptedBody.getDecryptedText().data, decryptedBody.getDecryptedText().size); u8 creatorNameLength = bodyDeserializer.extract<u8>(); @@ -513,7 +514,7 @@ namespace odhtdb return false; } - void Database::deserializeAddRequest(const std::shared_ptr<dht::Value> &value, const Hash &requestDataHash, const shared_ptr<char*> encryptionKey) + void Database::deserializeAddRequest(const shared_ptr<dht::Value> &value, const Hash &requestDataHash, const std::shared_ptr<Hash> &nodeHash, const shared_ptr<OwnedMemory> encryptionKey) { sibs::SafeDeserializer deserializer(value->data.data(), value->data.size()); char creatorPublicKeyRaw[PUBLIC_KEY_NUM_BYTES]; @@ -541,7 +542,7 @@ namespace odhtdb throw sibs::DeserializeException("Packet is from the future"); DatabaseOperation operation = deserializerUnsigned.extract<DatabaseOperation>(); - +#if 0 const Hash *node = databaseStorage.getNodeByUserPublicKey(creatorPublicKey); if(!node) { @@ -551,21 +552,21 @@ namespace odhtdb databaseStorage.addToQuarantine(requestDataHash, creatorPublicKey, creationDate, value->data.data(), value->data.size()); throw RequestQuarantineException(); } - - auto creatorUser = databaseStorage.getUserByPublicKey(creatorPublicKey); +#endif + auto creatorUser = databaseStorage.getUserByPublicKey(*nodeHash, creatorPublicKey); // TODO: Verify there isn't already data with same timestamp for this node. Same for quarantine. // TODO: We might receive 'add' data packet before 'create'. If that happens, we should put it in quarantine and process it later. - databaseStorage.appendStorage(*node, requestDataHash, creatorUser, creationDate, value->data.data(), value->data.size()); + databaseStorage.appendStorage(*nodeHash, requestDataHash, creatorUser, creationDate, value->data.data(), value->data.size()); if(operation == DatabaseOperation::ADD_DATA) { - if(deserializerUnsigned.getSize() < NONCE_BYTE_SIZE) + if(deserializerUnsigned.getSize() < ENCRYPTION_NONCE_BYTE_SIZE) throw sibs::DeserializeException("Unsigned encrypted body is too small (unable to extract nonce)"); - u8 nonce[NONCE_BYTE_SIZE]; - deserializerUnsigned.extract(nonce, NONCE_BYTE_SIZE); + u8 nonce[ENCRYPTION_NONCE_BYTE_SIZE]; + deserializerUnsigned.extract(nonce, ENCRYPTION_NONCE_BYTE_SIZE); DataView dataToDecrypt((void*)deserializerUnsigned.getBuffer(), deserializerUnsigned.getSize()); - Decryption decryptedBody(dataToDecrypt, DataView(nonce, NONCE_BYTE_SIZE), DataView(*encryptionKey, KEY_BYTE_SIZE)); + Decryption decryptedBody(dataToDecrypt, DataView(nonce, ENCRYPTION_NONCE_BYTE_SIZE), DataView(encryptionKey->data, ENCRYPTION_KEY_BYTE_SIZE)); if(!isUserAllowedToAddData(creatorUser)) { @@ -582,7 +583,7 @@ namespace odhtdb } Log::debug("Got add object, timestamp: %zu, data: %.*s", creationDate, decryptedBody.getDecryptedText().size, decryptedBody.getDecryptedText().data); - const DatabaseAddNodeRequest addNodeRequest(node, &requestDataHash, creationDate, creatorUser, decryptedBody.getDecryptedText()); + const DatabaseAddNodeRequest addNodeRequest(&*nodeHash, &requestDataHash, creationDate, creatorUser, decryptedBody.getDecryptedText()); if(onAddNodeCallbackFunc) onAddNodeCallbackFunc(addNodeRequest); } @@ -600,11 +601,13 @@ namespace odhtdb uint8_t groupId[16]; deserializerUnsigned.extract(groupId, 16); - auto group = databaseStorage.getGroupById(groupId); + auto group = databaseStorage.getGroupById(*nodeHash, groupId); if(group) { auto user = RemoteUser::create(userToAddPublicKey, name, group); - databaseStorage.addUser(user, *node); + // TODO: What if we receive packets in wrong order? (maliciously or non-maliciously). You would be able to register a user to a group with given name + // and further registration would be dropped (even if that is the correct one) + if(!databaseStorage.addUser(*nodeHash, user)) return; auto creatorUserGroupWithRights = getGroupWithRightsToAddUserToGroup(creatorUser->getGroups(), group); if(!creatorUserGroupWithRights) @@ -622,7 +625,7 @@ namespace odhtdb } Log::debug("Got add user object, timestamp: %zu, user added: %.*s", creationDate, nameLength, name.c_str()); - DatabaseAddUserRequest addUserRequest(node, &requestDataHash, creationDate, creatorUser, user, group); + DatabaseAddUserRequest addUserRequest(&*nodeHash, &requestDataHash, creationDate, creatorUser, user, group); if(onAddUserCallbackFunc) onAddUserCallbackFunc(addUserRequest); } @@ -639,7 +642,7 @@ namespace odhtdb } } - bool Database::listenCreateData(std::shared_ptr<dht::Value> value, const Hash &hash, const shared_ptr<char*> encryptionKey) + bool Database::listenCreateData(shared_ptr<dht::Value> value, const Hash &hash, const shared_ptr<OwnedMemory> encryptionKey) { Log::debug("Got create data"); try @@ -655,12 +658,12 @@ namespace odhtdb return true; } - bool Database::listenAddData(std::shared_ptr<dht::Value> value, const Hash &requestDataHash, const shared_ptr<char*> encryptionKey) + bool Database::listenAddData(shared_ptr<dht::Value> value, const Hash &requestDataHash, const std::shared_ptr<Hash> nodeHash, const shared_ptr<OwnedMemory> encryptionKey) { Log::debug("Got add data"); try { - deserializeAddRequest(value, requestDataHash, encryptionKey); + deserializeAddRequest(value, requestDataHash, nodeHash, encryptionKey); //Log::debug("Got add object, timestamp: %zu", addObject.timestamp); } catch (RequestQuarantineException &e) @@ -674,17 +677,17 @@ namespace odhtdb return true; } - void Database::setOnCreateNodeCallback(std::function<void(const DatabaseCreateNodeRequest&)> callbackFunc) + void Database::setOnCreateNodeCallback(function<void(const DatabaseCreateNodeRequest&)> callbackFunc) { onCreateNodeCallbackFunc = callbackFunc; } - void Database::setOnAddNodeCallback(std::function<void(const DatabaseAddNodeRequest&)> callbackFunc) + void Database::setOnAddNodeCallback(function<void(const DatabaseAddNodeRequest&)> callbackFunc) { onAddNodeCallbackFunc = callbackFunc; } - void Database::setOnAddUserCallback(std::function<void(const DatabaseAddUserRequest&)> callbackFunc) + void Database::setOnAddUserCallback(function<void(const DatabaseAddUserRequest&)> callbackFunc) { onAddUserCallbackFunc = callbackFunc; } diff --git a/src/DatabaseStorage.cpp b/src/DatabaseStorage.cpp index 212ca0f..bd98b8b 100644 --- a/src/DatabaseStorage.cpp +++ b/src/DatabaseStorage.cpp @@ -1,13 +1,28 @@ #include "../include/odhtdb/DatabaseStorage.hpp" -#include "../include/odhtdb/User.hpp" +#include "../include/odhtdb/RemoteUser.hpp" +#include "../include/odhtdb/LocalUser.hpp" +#include "../include/odhtdb/LocalUserEncrypted.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 <cstring> #include <chrono> +#include <boost/filesystem/convenience.hpp> +#include <sibs/SafeSerializer.hpp> +#include <sodium/randombytes.h> using namespace std; namespace odhtdb { + enum StorageType : u8 + { + STORAGE_TYPE_CREATE, + STORAGE_TYPE_APPEND + }; + const u64 QUARANTINE_STORAGE_TIME_MICROSECONDS = 60 * 1.0e6; DatabaseStorageObject::DatabaseStorageObject(DataView &_data, u64 _timestamp, const Signature::PublicKey &_creatorPublicKey) : @@ -23,6 +38,263 @@ namespace odhtdb storedTimestamp = chrono::duration_cast<chrono::microseconds>(time).count(); } + DatabaseStorage::DatabaseStorage(const boost::filesystem::path &storagePath) : + groupsFilePath(storagePath / "groups"), + usersFilePath(storagePath / "users"), + dataFilePath(storagePath / "data"), + metadataFilePath(storagePath / "metadata") + { + boost::filesystem::create_directories(storagePath); + + bool metadataLoaded = false; + try + { + loadMetadataFromFile(); + metadataLoaded = true; + loadGroupsFromFile(); + loadUsersFromFile(); + loadDataFromFile(); + //loadQuarantineFromFile(); + } + catch(FileException &e) + { + Log::warn("Failed to load data from file: %s, reason: %s. Ignoring...", dataFilePath.string().c_str(), e.what()); + if(!metadataLoaded) + { + sibs::SafeSerializer metadataSerializer; + metadataSerializer.add((u16)0); // Storage version + randombytes_buf(passwordSalt, PASSWORD_SALT_LEN); + metadataSerializer.add(passwordSalt, PASSWORD_SALT_LEN); + fileAppend(metadataFilePath, { metadataSerializer.getBuffer().data(), metadataSerializer.getBuffer().size() }); + } + } + } + + void DatabaseStorage::loadGroupsFromFile() + { + if(!boost::filesystem::exists(groupsFilePath)) return; + + OwnedMemory groupsFileContent = fileGetContent(groupsFilePath); + sibs::SafeDeserializer deserializer((u8*)groupsFileContent.data, groupsFileContent.size); + + while(!deserializer.empty()) + { + Hash nodeHash; + deserializer.extract((u8*)nodeHash.getData(), HASH_BYTE_SIZE); + + DataViewMap<Group*> *groupByIdMap = nullptr; + auto groupByIdMapIt = nodeGroupByIdMap.find(nodeHash); + if(groupByIdMapIt == nodeGroupByIdMap.end()) + { + groupByIdMap = new DataViewMap<Group*>(); + nodeGroupByIdMap[nodeHash] = groupByIdMap; + } + else + groupByIdMap = groupByIdMapIt->second; + + u8 groupNameSize = deserializer.extract<u8>(); + string groupName; + groupName.resize(groupNameSize); + deserializer.extract((u8*)&groupName[0], groupNameSize); + + u8 groupId[GROUP_ID_LENGTH]; + deserializer.extract(groupId, GROUP_ID_LENGTH); + + u8 permissionLevel = deserializer.extract<u8>(); + u32 permissionFlags = deserializer.extract<u32>(); + + Group *group = new Group(groupName, groupId, { permissionLevel, permissionFlags }); + (*groupByIdMap)[group->getId()] = group; + + string groupIdStr = bin2hex((const char*)groupId, GROUP_ID_LENGTH); + Log::debug("DatabaseStorage: Loaded group from file: %s", groupIdStr.c_str()); + } + } + + void DatabaseStorage::loadUsersFromFile() + { + if(!boost::filesystem::exists(usersFilePath)) return; + + OwnedMemory usersFileContent = fileGetContent(usersFilePath); + sibs::SafeDeserializer deserializer((u8*)usersFileContent.data, usersFileContent.size); + + while(!deserializer.empty()) + { + Hash nodeHash; + deserializer.extract((u8*)nodeHash.getData(), HASH_BYTE_SIZE); + + Signature::MapPublicKey<UserData*> *publicKeyUserDataMap = nullptr; + auto publicKeyUserDataMapIt = nodePublicKeyUserDataMap.find(nodeHash); + if(publicKeyUserDataMapIt == nodePublicKeyUserDataMap.end()) + { + publicKeyUserDataMap = new Signature::MapPublicKey<UserData*>(); + nodePublicKeyUserDataMap[nodeHash] = publicKeyUserDataMap; + } + else + publicKeyUserDataMap = publicKeyUserDataMapIt->second; + + DataViewMap<Group*> *groupByIdMap = nullptr; + auto groupByIdMapIt = nodeGroupByIdMap.find(nodeHash); + if(groupByIdMapIt == nodeGroupByIdMap.end()) + { + groupByIdMap = new DataViewMap<Group*>(); + nodeGroupByIdMap[nodeHash] = groupByIdMap; + } + else + groupByIdMap = groupByIdMapIt->second; + + User::Type userType = deserializer.extract<User::Type>(); + + u8 usernameSize = deserializer.extract<u8>(); + string username; + username.resize(usernameSize); + deserializer.extract((u8*)&username[0], usernameSize); + + u8 userPublicKeyRaw[PUBLIC_KEY_NUM_BYTES]; + deserializer.extract(userPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); + Signature::PublicKey userPublicKey((const char*)userPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); + + UserData *userData = new UserData(); + if(userType == User::Type::LOCAL) + { + EncryptedPrivateKey encryptedPrivateKey; + deserializer.extract(encryptedPrivateKey.nonce, ENCRYPTION_NONCE_BYTE_SIZE); + deserializer.extract(encryptedPrivateKey.encryptedPrivateKey, 16 + PRIVATE_KEY_NUM_BYTES); + userData->user = LocalUserEncrypted::create(userPublicKey, encryptedPrivateKey, username, nullptr); + } + else + { + userData->user = RemoteUser::create(userPublicKey, username, nullptr); + } + + u8 numGroups = deserializer.extract<u8>(); + for(int i = 0; i < numGroups; ++i) + { + u8 groupId[GROUP_ID_LENGTH]; + deserializer.extract(groupId, GROUP_ID_LENGTH); + + auto groupIt = groupByIdMap->find(DataView(groupId, GROUP_ID_LENGTH)); + if(groupIt == groupByIdMap->end()) + { + string errMsg = "User group with id "; + errMsg += bin2hex((const char*)groupId, GROUP_ID_LENGTH); + errMsg += " does not exist"; + throw DatabaseStorageCorrupt(errMsg); + } + userData->user->addToGroup(groupIt->second); + } + + (*publicKeyUserDataMap)[userData->user->getPublicKey()] = userData; + } + } + + void DatabaseStorage::loadStorageCreate(sibs::SafeDeserializer &deserializer) + { + u64 timestamp = deserializer.extract<u64>(); + + u8 groupId[GROUP_ID_LENGTH]; + deserializer.extract(groupId, GROUP_ID_LENGTH); + + u32 dataSize = deserializer.extract<u32>(); + u8 *data = new u8[dataSize]; + deserializer.extract(data, dataSize); + + Hash nodeHash; + deserializer.extract((u8*)nodeHash.getData(), HASH_BYTE_SIZE); + + auto groupByIdMapIt = nodeGroupByIdMap.find(nodeHash); + if(groupByIdMapIt == nodeGroupByIdMap.end()) + { + string errMsg = "No groups exists in node "; + errMsg += nodeHash.toString(); + throw DatabaseStorageCorrupt(errMsg); + } + + auto groupIt = groupByIdMapIt->second->find(DataView(groupId, GROUP_ID_LENGTH)); + if(groupIt == groupByIdMapIt->second->end()) + { + string errMsg = "User group with id "; + errMsg += bin2hex((const char*)groupId, GROUP_ID_LENGTH); + errMsg += " does not exist in node "; + errMsg += nodeHash.toString(); + throw DatabaseStorageCorrupt(errMsg); + } + + DatabaseStorageObjectList *databaseStorageObjectList = new DatabaseStorageObjectList(); + databaseStorageObjectList->createdTimestamp = timestamp; + databaseStorageObjectList->groups.push_back(groupIt->second); + databaseStorageObjectList->data = DataView(data, dataSize); + storageMap[nodeHash] = databaseStorageObjectList; + } + + void DatabaseStorage::loadStorageAppend(sibs::SafeDeserializer &deserializer) + { + u64 timestamp = deserializer.extract<u64>(); + + u8 creatorPublicKeyRaw[PUBLIC_KEY_NUM_BYTES]; + deserializer.extract(creatorPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); + Signature::PublicKey creatorPublicKey((const char*)creatorPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); + + u32 dataSize = deserializer.extract<u32>(); + u8 *data = new u8[dataSize]; + deserializer.extract(data, dataSize); + + Hash nodeHash; + deserializer.extract((u8*)nodeHash.getData(), HASH_BYTE_SIZE); + + Hash dataHash; + deserializer.extract((u8*)dataHash.getData(), HASH_BYTE_SIZE); + storedDataHash.insert(dataHash); + + auto storageIt = storageMap.find(nodeHash); + if(storageIt == storageMap.end()) + { + string errMsg = "Database storage with hash "; + errMsg += nodeHash.toString(); + errMsg += " not found"; + throw DatabaseStorageCorrupt(errMsg); + } + + DataView storageData { data, dataSize }; + DatabaseStorageObject *databaseStorageObject = new DatabaseStorageObject(storageData, timestamp, creatorPublicKey); + storageIt->second->objects.push_back(databaseStorageObject); + } + + void DatabaseStorage::loadDataFromFile() + { + if(!boost::filesystem::exists(dataFilePath)) return; + + OwnedMemory dataFileContent = fileGetContent(dataFilePath); + sibs::SafeDeserializer deserializer((u8*)dataFileContent.data, dataFileContent.size); + + while(!deserializer.empty()) + { + StorageType storageType = deserializer.extract<StorageType>(); + switch(storageType) + { + case STORAGE_TYPE_CREATE: + loadStorageCreate(deserializer); + break; + case STORAGE_TYPE_APPEND: + loadStorageAppend(deserializer); + break; + } + } + } + + void DatabaseStorage::loadMetadataFromFile() + { + OwnedMemory metadataFileContent = fileGetContent(metadataFilePath); + sibs::SafeDeserializer deserializer((u8*)metadataFileContent.data, metadataFileContent.size); + + u16 storageVersion = deserializer.extract<u16>(); + + u8 passwordSalt[PASSWORD_SALT_LEN]; + deserializer.extract(passwordSalt, PASSWORD_SALT_LEN); + + assert(deserializer.empty()); + } + void DatabaseStorage::createStorage(const Hash &hash, Group *creatorGroup, u64 timestamp, const u8 *data, usize dataSize) { if(storageMap.find(hash) != storageMap.end()) @@ -33,18 +305,30 @@ namespace odhtdb throw DatabaseStorageAlreadyExists(errMsg); } + addGroup(hash, creatorGroup); + for(auto user : creatorGroup->getUsers()) + { + addUser(hash, (User*)user); + } + DatabaseStorageObjectList *databaseStorageObjectList = new DatabaseStorageObjectList(); databaseStorageObjectList->createdTimestamp = timestamp; databaseStorageObjectList->groups.push_back(creatorGroup); databaseStorageObjectList->data = DataView(new u8[dataSize], dataSize); memcpy(databaseStorageObjectList->data.data, data, dataSize); storageMap[hash] = databaseStorageObjectList; - groupByIdMap[creatorGroup->getId()] = creatorGroup; - for(auto user : creatorGroup->getUsers()) - { - addUser((User*)user, hash); - } + sibs::SafeSerializer serializer; + serializer.add(STORAGE_TYPE_CREATE); + serializer.add(timestamp); + serializer.add((u8*)creatorGroup->getId().data, GROUP_ID_LENGTH); + + serializer.add((u32)dataSize); + serializer.add(data, dataSize); + + serializer.add((u8*)hash.getData(), HASH_BYTE_SIZE); + + fileAppend(dataFilePath, { serializer.getBuffer().data(), serializer.getBuffer().size() }); } void DatabaseStorage::appendStorage(const Hash &nodeHash, const Hash &dataHash, const User *creatorUser, u64 timestamp, const u8 *data, usize dataSize) @@ -70,6 +354,19 @@ namespace odhtdb memcpy(storageData.data, data, dataSize); DatabaseStorageObject *databaseStorageObject = new DatabaseStorageObject(storageData, timestamp, creatorUser->getPublicKey()); it->second->objects.push_back(databaseStorageObject); + + sibs::SafeSerializer serializer; + serializer.add(STORAGE_TYPE_APPEND); + serializer.add(timestamp); + serializer.add((u8*)creatorUser->getPublicKey().getData(), PUBLIC_KEY_NUM_BYTES); + + serializer.add((u32)dataSize); + serializer.add(data, dataSize); + + serializer.add((u8*)nodeHash.getData(), HASH_BYTE_SIZE); + serializer.add((u8*)dataHash.getData(), HASH_BYTE_SIZE); + + fileAppend(dataFilePath, { serializer.getBuffer().data(), serializer.getBuffer().size() }); } void DatabaseStorage::addToQuarantine(const Hash &dataHash, const Signature::PublicKey &creatorPublicKey, u64 timestamp, const u8 *data, usize dataSize) @@ -88,10 +385,103 @@ namespace odhtdb quarantineStorageMap[creatorPublicKey].emplace_back(databaseQuarantineStorageObject); } - void DatabaseStorage::addUser(User *user, const Hash &hash) + bool DatabaseStorage::addGroup(const Hash &nodeHash, Group *group) + { + DataViewMap<Group*> *groupByIdMap = nullptr; + auto groupByIdMapIt = nodeGroupByIdMap.find(nodeHash); + if(groupByIdMapIt == nodeGroupByIdMap.end()) + { + groupByIdMap = new DataViewMap<Group*>(); + nodeGroupByIdMap[nodeHash] = groupByIdMap; + } + else + groupByIdMap = groupByIdMapIt->second; + + if(groupByIdMap->find(group->getId()) != groupByIdMap->end()) + return false; + + (*groupByIdMap)[group->getId()] = group; + + sibs::SafeSerializer serializer; + + serializer.add((u8*)nodeHash.getData(), HASH_BYTE_SIZE); + + serializer.add((u8)group->getName().size()); + serializer.add((u8*)group->getName().data(), group->getName().size()); + + serializer.add((u8*)group->getId().data, GROUP_ID_LENGTH); + + serializer.add(group->getPermission().getPermissionLevel()); + serializer.add(group->getPermission().getPermissionFlags()); + + fileAppend(groupsFilePath, DataView(serializer.getBuffer().data(), serializer.getBuffer().size())); + + return true; + } + + bool DatabaseStorage::addUser(const Hash &nodeHash, User *user) { - userPublicKeyNodeMap[user->getPublicKey()] = new Hash(hash); - publicKeyUserMap[user->getPublicKey()] = user; + Signature::MapPublicKey<UserData*> *publicKeyUserDataMap = nullptr; + auto publicKeyUserDataMapIt = nodePublicKeyUserDataMap.find(nodeHash); + if(publicKeyUserDataMapIt == nodePublicKeyUserDataMap.end()) + { + publicKeyUserDataMap = new Signature::MapPublicKey<UserData*>(); + nodePublicKeyUserDataMap[nodeHash] = publicKeyUserDataMap; + } + else + publicKeyUserDataMap = publicKeyUserDataMapIt->second; + + if(publicKeyUserDataMap->find(user->getPublicKey()) != publicKeyUserDataMap->end()) + return false; + + UserData *userData = new UserData(); + userData->user = user; + if(user->getType() == User::Type::LOCAL) + { + LocalUser *localUser = static_cast<LocalUser*>(user); + //DataView plainPassword((void*)localUser->getPlainPassword().data(), localUser->getPlainPassword().size()); + OwnedMemory hashedPassword = hashPassword(DataView((void*)localUser->getPlainPassword().data(), localUser->getPlainPassword().size()), DataView(passwordSalt, PASSWORD_SALT_LEN)); + memcpy(userData->hashedPassword, hashedPassword.data, hashedPassword.size); + } + else + { + memset(userData->hashedPassword, 0, HASHED_PASSWORD_LEN); + } + (*publicKeyUserDataMap)[user->getPublicKey()] = userData; + + // TODO: Instead of directly saving user to file, maybe user should be added to buffer and then save to file after we have several users (performance improvement) + + sibs::SafeSerializer serializer; + + serializer.add((u8*)nodeHash.getData(), HASH_BYTE_SIZE); + + serializer.add(user->getType()); + + serializer.add((u8)user->getName().size()); + serializer.add((u8*)user->getName().data(), user->getName().size()); + + serializer.add((u8*)user->getPublicKey().getData(), PUBLIC_KEY_NUM_BYTES); + if(user->getType() == User::Type::LOCAL) + { + LocalUser *localUser = static_cast<LocalUser*>(user); + static_assert(HASHED_PASSWORD_LEN == ENCRYPTION_KEY_BYTE_SIZE, "Encryption key size has changed but hashed password size hasn't"); + Encryption encryptedPrivateKey(DataView((void*)localUser->getPrivateKey().getData(), localUser->getPrivateKey().getSize()), + DataView(), + DataView(userData->hashedPassword, HASHED_PASSWORD_LEN)); + serializer.add((u8*)encryptedPrivateKey.getNonce().data, ENCRYPTION_NONCE_BYTE_SIZE); + assert(16 + PRIVATE_KEY_NUM_BYTES == encryptedPrivateKey.getCipherText().size); + serializer.add((u8*)encryptedPrivateKey.getCipherText().data, encryptedPrivateKey.getCipherText().size); + } + + serializer.add((u8)user->getGroups().size()); + for(Group *group : user->getGroups()) + { + serializer.add((u8*)group->getId().data, GROUP_ID_LENGTH); + } + + fileAppend(usersFilePath, DataView(serializer.getBuffer().data(), serializer.getBuffer().size())); + + return true; } const DatabaseStorageObjectList* DatabaseStorage::getStorage(const Hash &hash) const @@ -102,33 +492,41 @@ namespace odhtdb return nullptr; } - const Hash* DatabaseStorage::getNodeByUserPublicKey(const Signature::PublicKey &userPublicKey) const + Group* DatabaseStorage::getGroupById(const Hash &nodeHash, uint8_t groupId[GROUP_ID_LENGTH]) { - auto it = userPublicKeyNodeMap.find(userPublicKey); - if(it != userPublicKeyNodeMap.end()) - return it->second; + auto groupByIdMapIt = nodeGroupByIdMap.find(nodeHash); + if(groupByIdMapIt != nodeGroupByIdMap.end()) + { + auto it = groupByIdMapIt->second->find(DataView(groupId, GROUP_ID_LENGTH)); + if(it != groupByIdMapIt->second->end()) + return it->second; + } return nullptr; } - // Returns nullptr if no user with public key exists - const User* DatabaseStorage::getUserByPublicKey(const Signature::PublicKey &userPublicKey) const + User* DatabaseStorage::getUserByPublicKey(const Hash &nodeHash, Signature::PublicKey &userPublicKey) { - auto it = publicKeyUserMap.find(userPublicKey); - if(it != publicKeyUserMap.end()) - return it->second; + auto publicKeyUserDataMapIt = nodePublicKeyUserDataMap.find(nodeHash); + if(publicKeyUserDataMapIt != nodePublicKeyUserDataMap.end()) + { + auto it = publicKeyUserDataMapIt->second->find(userPublicKey); + if(it != publicKeyUserDataMapIt->second->end()) + return it->second->user; + } return nullptr; } - Group* DatabaseStorage::getGroupById(uint8_t groupId[16]) + const Signature::MapPublicKey<DatabaseStorage::UserData*>* DatabaseStorage::getUsersData(const Hash &nodeHash) const { - auto it = groupByIdMap.find(DataView(groupId, 16)); - if(it != groupByIdMap.end()) - return it->second; + auto publicKeyUserDataMapIt = nodePublicKeyUserDataMap.find(nodeHash); + if(publicKeyUserDataMapIt != nodePublicKeyUserDataMap.end()) + return publicKeyUserDataMapIt->second; return nullptr; } void DatabaseStorage::update() { + // TODO: Modify this to iterate backwards. Because list is sorted in order of timestamp, we can remove data in range auto time = chrono::high_resolution_clock::now().time_since_epoch(); auto timeMicroseconds = chrono::duration_cast<chrono::microseconds>(time).count(); for(auto mapIt = quarantineStorageMap.begin(); mapIt != quarantineStorageMap.end(); ) diff --git a/src/Encryption.cpp b/src/Encryption.cpp index 7f8700b..e67c719 100644 --- a/src/Encryption.cpp +++ b/src/Encryption.cpp @@ -12,14 +12,14 @@ namespace odhtdb if(_key.data) { - if(_key.size != KEY_BYTE_SIZE) + if(_key.size != ENCRYPTION_KEY_BYTE_SIZE) throw EncryptionException("Encryption key is wrong size"); memcpy(key, _key.data, _key.size); } else crypto_aead_xchacha20poly1305_ietf_keygen(key); - randombytes_buf(nonce, NONCE_BYTE_SIZE); + randombytes_buf(nonce, ENCRYPTION_NONCE_BYTE_SIZE); if(crypto_aead_xchacha20poly1305_ietf_encrypt(cipherText, &cipherTextLength, (const unsigned char*)data.data, data.size, (const unsigned char*)additionalData.data, additionalData.size, nullptr, nonce, key) < 0) throw EncryptionException("Failed to encrypt data"); } @@ -31,12 +31,12 @@ namespace odhtdb DataView Encryption::getKey() const { - return DataView((void*)key, KEY_BYTE_SIZE); + return DataView((void*)key, ENCRYPTION_KEY_BYTE_SIZE); } DataView Encryption::getNonce() const { - return DataView((void*)nonce, NONCE_BYTE_SIZE); + return DataView((void*)nonce, ENCRYPTION_NONCE_BYTE_SIZE); } DataView Encryption::getCipherText() const @@ -49,10 +49,10 @@ namespace odhtdb decryptedText = new unsigned char[data.size]; decryptedTextLength = data.size; - if(nonce.size < NONCE_BYTE_SIZE) + if(nonce.size < ENCRYPTION_NONCE_BYTE_SIZE) throw DecryptionException("Nonce is not big enough"); - if(key.size < KEY_BYTE_SIZE) + if(key.size < ENCRYPTION_KEY_BYTE_SIZE) throw DecryptionException("Key is not big enough"); if(crypto_aead_xchacha20poly1305_ietf_decrypt(decryptedText, &decryptedTextLength, nullptr, (const unsigned char*)data.data, data.size, nullptr, 0, (const unsigned char*)nonce.data, (const unsigned char*)key.data) < 0) diff --git a/src/FileUtils.cpp b/src/FileUtils.cpp new file mode 100644 index 0000000..3a78a63 --- /dev/null +++ b/src/FileUtils.cpp @@ -0,0 +1,57 @@ +#include "../include/odhtdb/FileUtils.hpp" +#include "../include/odhtdb/env.hpp" + +using namespace std; + +namespace odhtdb +{ + OwnedMemory fileGetContent(const boost::filesystem::path &filepath) + { +#if OS_FAMILY == OS_FAMILY_POSIX + FILE *file = fopen(filepath.string().c_str(), "rb"); +#else + FILE *file = _wfopen(filepath.wstring().c_str(), L"rb"); +#endif + if(!file) + { + int error = errno; + string errMsg = "Failed to open file: "; + errMsg += filepath.string(); + errMsg += "; reason: "; + errMsg += strerror(error); + throw FileException(errMsg); + } + + fseek(file, 0, SEEK_END); + size_t fileSize = ftell(file); + fseek(file, 0, SEEK_SET); + + char *result = new char[fileSize]; + fread(result, 1, fileSize, file); + fclose(file); + return { result, fileSize }; + } + + void fileAppend(const boost::filesystem::path &filepath, const DataView &data) + { +#if OS_FAMILY == OS_FAMILY_POSIX + FILE *file = fopen(filepath.string().c_str(), "ab+"); +#else + FILE *file = _wfopen(filepath.wstring().c_str(), L"ab+"); +#endif + if(!file) + { + int error = errno; + string errMsg = "Failed to append to file: "; + errMsg += filepath.string(); + errMsg += "; reason: "; + errMsg += strerror(error); + throw FileException(errMsg); + } + + flockfile(file); + setbuf(file, NULL); + fwrite(data.data, 1, data.size, file); + fclose(file); + } +} diff --git a/src/Group.cpp b/src/Group.cpp index caf3e05..a99fdf6 100644 --- a/src/Group.cpp +++ b/src/Group.cpp @@ -6,13 +6,13 @@ using namespace std; namespace odhtdb { - Group::Group(const string &_name, uint8_t _id[16], const Permission &_permission) : + Group::Group(const string &_name, uint8_t _id[GROUP_ID_LENGTH], const Permission &_permission) : name(_name), permission(_permission) { if(name.size() > 255) throw GroupNameTooLongException(name); - memcpy(id, _id, 16); + memcpy(id, _id, GROUP_ID_LENGTH); } Group::~Group() @@ -32,7 +32,7 @@ namespace odhtdb DataView Group::getId() const { - return { (void*)id, 16 }; + return { (void*)id, GROUP_ID_LENGTH }; } const Permission& Group::getPermission() const diff --git a/src/LocalUser.cpp b/src/LocalUser.cpp new file mode 100644 index 0000000..660d09a --- /dev/null +++ b/src/LocalUser.cpp @@ -0,0 +1,12 @@ +#include "../include/odhtdb/LocalUser.hpp" + +namespace odhtdb +{ + LocalUser::LocalUser(const Signature::KeyPair &_keyPair, const std::string &name, Group *group, const std::string &_plainPassword) : + User(User::Type::LOCAL, name, group), + keyPair(_keyPair), + plainPassword(_plainPassword) + { + + } +} diff --git a/src/LocalUserEncrypted.cpp b/src/LocalUserEncrypted.cpp new file mode 100644 index 0000000..1c22488 --- /dev/null +++ b/src/LocalUserEncrypted.cpp @@ -0,0 +1,27 @@ +#include "../include/odhtdb/LocalUserEncrypted.hpp" +#include "../include/odhtdb/PasswordHash.hpp" +#include <cstring> + +namespace odhtdb +{ + EncryptedPrivateKey::EncryptedPrivateKey() + { + memset(nonce, 0, ENCRYPTION_NONCE_BYTE_SIZE); + memset(encryptedPrivateKey, 0, 16 + PRIVATE_KEY_NUM_BYTES); + } + + EncryptedPrivateKey::EncryptedPrivateKey(const EncryptedPrivateKey &other) + { + memcpy(nonce, other.nonce, ENCRYPTION_NONCE_BYTE_SIZE); + memcpy(encryptedPrivateKey, other.encryptedPrivateKey, 16 + PRIVATE_KEY_NUM_BYTES); + } + + Signature::PrivateKey EncryptedPrivateKey::decrypt(const DataView &plainPassword, const DataView &salt) const + { + OwnedMemory hashedPassword = hashPassword(plainPassword, salt); + Decryption decryptedPrivateKey(DataView((void*)encryptedPrivateKey, 16 + PRIVATE_KEY_NUM_BYTES), + DataView((void*)nonce, ENCRYPTION_NONCE_BYTE_SIZE), + DataView(hashedPassword.data, hashedPassword.size)); + return { (const char*)decryptedPrivateKey.getDecryptedText().data, decryptedPrivateKey.getDecryptedText().size }; + } +} diff --git a/src/Log.cpp b/src/Log.cpp index 9c06a62..0bdc0a6 100644 --- a/src/Log.cpp +++ b/src/Log.cpp @@ -2,15 +2,14 @@ #include <cstdarg> #include <mutex> -static std::mutex mutexDebug; - namespace odhtdb { // TODO: Add color (if output is tty)? + static std::mutex mutexLog; void Log::debug(const char *fmt, ...) { - std::lock_guard<std::mutex> lock(mutexDebug); + std::lock_guard<std::mutex> lock(mutexLog); va_list args; va_start(args, fmt); fputs("Debug: ", stdout); @@ -21,7 +20,7 @@ namespace odhtdb void Log::warn(const char *fmt, ...) { - std::lock_guard<std::mutex> lock(mutexDebug); + std::lock_guard<std::mutex> lock(mutexLog); va_list args; va_start(args, fmt); fputs("Warning: ", stdout); @@ -32,7 +31,7 @@ namespace odhtdb void Log::error(const char *fmt, ...) { - std::lock_guard<std::mutex> lock(mutexDebug); + std::lock_guard<std::mutex> lock(mutexLog); va_list args; va_start(args, fmt); fputs("Error: ", stderr); diff --git a/src/OwnedMemory.cpp b/src/OwnedMemory.cpp new file mode 100644 index 0000000..1d53faf --- /dev/null +++ b/src/OwnedMemory.cpp @@ -0,0 +1,21 @@ +#include "../include/odhtdb/OwnedMemory.hpp" +#include <cstdlib> + +namespace odhtdb +{ + OwnedMemory::OwnedMemory(OwnedMemory &&other) + { + data = other.data; + size = other.size; + + other.data = nullptr; + other.size = 0; + } + + OwnedMemory::~OwnedMemory() + { + free(data); + data = nullptr; + size = 0; + } +} diff --git a/src/PasswordHash.cpp b/src/PasswordHash.cpp new file mode 100644 index 0000000..f6d3713 --- /dev/null +++ b/src/PasswordHash.cpp @@ -0,0 +1,23 @@ +#include "../include/odhtdb/PasswordHash.hpp" +#include <argon2.h> + +namespace odhtdb +{ + OwnedMemory hashPassword(const DataView &plainPassword, const DataView &salt) + { + OwnedMemory result; + + const uint32_t tCost = 2; + const uint32_t mCost = 1 << 16; + const uint32_t parallelism = 1; + const uint32_t HASHLEN = 32; + + result.data = new uint8_t[HASHLEN]; + result.size = HASHLEN; + + if(argon2i_hash_raw(tCost, mCost, parallelism, plainPassword.data, plainPassword.size, salt.data, salt.size, result.data, HASHLEN) != ARGON2_OK) + throw std::runtime_error("Failed to hash password"); + + return result; + } +} diff --git a/src/Permission.cpp b/src/Permission.cpp index 57c1f11..dc1e9b6 100644 --- a/src/Permission.cpp +++ b/src/Permission.cpp @@ -2,6 +2,13 @@ namespace odhtdb { + Permission::Permission(u8 _permissionLevel, u32 _permissionFlags) : + permissionLevel(_permissionLevel), + permissionFlags(_permissionFlags) + { + + } + Permission::Permission(u8 _permissionLevel, std::initializer_list<PermissionType> permissions) : permissionLevel(_permissionLevel) { diff --git a/src/Signature.cpp b/src/Signature.cpp index b6fb12a..3fd52ee 100644 --- a/src/Signature.cpp +++ b/src/Signature.cpp @@ -64,6 +64,8 @@ namespace odhtdb return bin2hex(data, PUBLIC_KEY_NUM_BYTES); } + PrivateKey PrivateKey::ZERO("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", PRIVATE_KEY_NUM_BYTES); + PrivateKey::PrivateKey(const char *_data, size_t size) { if(size != PRIVATE_KEY_NUM_BYTES) @@ -113,5 +115,12 @@ namespace odhtdb if(crypto_sign_ed25519_keypair((unsigned char*)publicKey.data, (unsigned char*)privateKey.data) < 0) throw SignatureGenerationException("Failed to generate signature keypair"); } + + KeyPair::KeyPair(const PublicKey &_publicKey, const PrivateKey &_privateKey) : + publicKey(_publicKey), + privateKey(_privateKey) + { + + } } } diff --git a/src/User.cpp b/src/User.cpp index 7b90872..1fb4a11 100644 --- a/src/User.cpp +++ b/src/User.cpp @@ -3,7 +3,7 @@ namespace odhtdb { - User::User(const std::string &_name, Group *group) : name(_name) + User::User(Type _type, const std::string &_name, Group *group) : type(_type), name(_name) { if(name.size() > 255) throw UserNameTooLongException(name); diff --git a/tests/main.cpp b/tests/main.cpp index bd7d75c..d509972 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -5,6 +5,9 @@ #include "../include/odhtdb/LocalUser.hpp" #include "../include/odhtdb/Encryption.hpp" #include "../include/odhtdb/Hash.hpp" +#include "../include/odhtdb/hex2bin.hpp" +#include "../include/odhtdb/bin2hex.hpp" +#include "../include/odhtdb/DataView.hpp" #include <vector> #include <chrono> #include <thread> @@ -13,10 +16,34 @@ using namespace std; using namespace chrono_literals; using namespace odhtdb; +void testBinHexConvert() +{ + DataView input { (void*)"hello", 5 }; + + string inputHex = bin2hex((const char*)input.data, input.size); + assertEquals<string>("68656c6c6f", inputHex); + + string inputBin = hex2bin(inputHex.c_str(), inputHex.size()); + if(inputBin.size() != input.size) + { + string errMsg = "Expected input converted to hex and then back to binary size to be 5, was: "; + errMsg += to_string(inputBin.size()); + fail(errMsg); + } + + if(memcmp(input.data, inputBin.data(), input.size) != 0) + { + string errMsg = "Expected input converted to hex and then back to binary to be same as original input ('hello'), was: "; + errMsg += string(inputBin.c_str(), inputBin.size()); + fail(errMsg); + } +} + void testHash() { Hash hash("odhtdb", 6); - assertEquals<string>("a7b30ec8ab92de60e551b26bb8f78d315697f84dd7f5549a143477e095ec934f", hash.toString()); + string hashHex = hash.toString(); + assertEquals<string>("a7b30ec8ab92de60e551b26bb8f78d315697f84dd7f5549a143477e095ec934f", hashHex); Log::debug("hash of 'odhtdb' is: a7b30ec8ab92de60e551b26bb8f78d315697f84dd7f5549a143477e095ec934f"); } @@ -84,10 +111,12 @@ void testEncryption() int main() { Log::debug("Starting tests..."); - LocalUser *localUser = LocalUser::create(Signature::KeyPair(), "dec05eba", nullptr); - testSignData(localUser); - testEncryption(); + testBinHexConvert(); testHash(); + testEncryption(); + + LocalUser *localUser = LocalUser::create(Signature::KeyPair(), "dec05eba", nullptr, "secretPassword"); + testSignData(localUser); // TODO: Setup local bootstrap node for tests Database database("bootstrap.ring.cx", 4222, "storage"); @@ -107,16 +136,16 @@ int main() Log::debug("Add user callback"); }); - auto databaseCreateResponse = database.create("adminUserName", "latenight"); + auto databaseCreateResponse = database.create("adminUserName", "secretPassword", "latenight"); DatabaseNode databaseNode(databaseCreateResponse->getNodeEncryptionKey(), databaseCreateResponse->getRequestHash()); auto adminUser = (LocalUser*)databaseCreateResponse->getNodeAdminUser(); database.addData(databaseNode, adminUser, DataView{ (void*)"hello, world!", 13 }); - database.addUserToGroup(databaseNode, adminUser, localUser->getName(), localUser->getPublicKey(), adminUser->getGroups()[0]); + database.addUser(databaseNode, adminUser, localUser->getName(), localUser->getPublicKey(), adminUser->getGroups()[0]); localUser->addToGroup(adminUser->getGroups()[0]); database.addData(databaseNode, localUser, DataView{ (void*)"hello, aaald!", 13 }); database.commit(); - database.seed(databaseCreateResponse->getRequestHash(), databaseCreateResponse->getNodeEncryptionKey()); + database.seed(databaseNode); auto start = chrono::high_resolution_clock::now(); while(chrono::high_resolution_clock::now() - start < 3s) { |