aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2018-04-14 19:45:15 +0200
committerdec05eba <dec05eba@protonmail.com>2020-08-18 23:25:46 +0200
commit414f2cafc6cf2fe141c011b0d63d447a9b983ac3 (patch)
treea0d53ca898b8586e7be97689fcb993d5405d08a0
parent53a1850ce2a253c574113684ac38c8ed31b1d0ae (diff)
Store database storage to files, also loading
-rw-r--r--.gitignore1
-rw-r--r--README.md7
-rw-r--r--include/odhtdb/Database.hpp27
-rw-r--r--include/odhtdb/DatabaseNode.hpp9
-rw-r--r--include/odhtdb/DatabaseStorage.hpp57
-rw-r--r--include/odhtdb/Encryption.hpp8
-rw-r--r--include/odhtdb/FileUtils.hpp22
-rw-r--r--include/odhtdb/Group.hpp6
-rw-r--r--include/odhtdb/LocalUser.hpp13
-rw-r--r--include/odhtdb/LocalUserEncrypted.hpp51
-rw-r--r--include/odhtdb/OwnedMemory.hpp22
-rw-r--r--include/odhtdb/PasswordHash.hpp9
-rw-r--r--include/odhtdb/Permission.hpp3
-rw-r--r--include/odhtdb/RemoteUser.hpp2
-rw-r--r--include/odhtdb/Signature.hpp7
-rw-r--r--include/odhtdb/User.hpp12
-rw-r--r--include/odhtdb/env.hpp63
-rw-r--r--include/odhtdb/hex2bin.hpp60
-rw-r--r--project.conf1
-rw-r--r--src/Database.cpp107
-rw-r--r--src/DatabaseStorage.cpp442
-rw-r--r--src/Encryption.cpp12
-rw-r--r--src/FileUtils.cpp57
-rw-r--r--src/Group.cpp6
-rw-r--r--src/LocalUser.cpp12
-rw-r--r--src/LocalUserEncrypted.cpp27
-rw-r--r--src/Log.cpp9
-rw-r--r--src/OwnedMemory.cpp21
-rw-r--r--src/PasswordHash.cpp23
-rw-r--r--src/Permission.cpp7
-rw-r--r--src/Signature.cpp9
-rw-r--r--src/User.cpp2
-rw-r--r--tests/main.cpp43
33 files changed, 1023 insertions, 134 deletions
diff --git a/.gitignore b/.gitignore
index 38f9679..88bf288 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
sibs-build/
.kdev4/
odhtdb.kdev4
+storage/
diff --git a/README.md b/README.md
index 7a81580..abe8a0d 100644
--- a/README.md
+++ b/README.md
@@ -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)
{