diff options
author | Aleksi Lindeman <0xdec05eba@gmail.com> | 2018-03-05 22:45:56 +0100 |
---|---|---|
committer | Aleksi Lindeman <0xdec05eba@gmail.com> | 2018-03-05 22:48:26 +0100 |
commit | 2ffb47d0043e57707474e5ae811f97c2e5e93f25 (patch) | |
tree | fd60b300cdf736de5adc68b395105dcfc6a43f09 | |
parent | 66661e47dc826f50b690e080057f47a0ea27016c (diff) |
Implement 'create' operation, add seeding
Seeding is currently only done on the key you specify, in the future
the user should request data that it can seed.
-rw-r--r-- | README.md | 7 | ||||
-rw-r--r-- | Scheme.md | 18 | ||||
-rw-r--r-- | include/Database.hpp | 121 | ||||
-rw-r--r-- | include/DatabaseStorage.hpp | 18 | ||||
-rw-r--r-- | include/DhtKey.hpp | 19 | ||||
-rw-r--r-- | include/Encryption.hpp | 56 | ||||
-rw-r--r-- | include/Group.hpp | 8 | ||||
-rw-r--r-- | include/Hash.hpp | 46 | ||||
-rw-r--r-- | include/Key.hpp | 29 | ||||
-rw-r--r-- | include/StagedObject.hpp | 38 | ||||
-rw-r--r-- | include/bin2hex.hpp | 23 | ||||
-rw-r--r-- | src/Database.cpp | 385 | ||||
-rw-r--r-- | src/DatabaseStorage.cpp | 36 | ||||
-rw-r--r-- | src/DhtKey.cpp | 22 | ||||
-rw-r--r-- | src/Encryption.cpp | 60 | ||||
-rw-r--r-- | src/Group.cpp | 11 | ||||
-rw-r--r-- | src/Hash.cpp | 55 | ||||
-rw-r--r-- | src/Signature.cpp | 6 | ||||
-rw-r--r-- | tests/assert.hpp | 20 | ||||
-rw-r--r-- | tests/main.cpp | 48 |
20 files changed, 756 insertions, 270 deletions
@@ -1,8 +1,9 @@ # odhtdb Decentralized key-value database using OpenDHT for decentralized communication. ## End-to-end encryption -Data is signed using ed25519 and encrypted using XChaCha20. See src/Encryption.cpp and src/Signature.cpp. - +Data is signed using ed25519, encrypted using xchacha20-poly1305 ietf and hashed using Blake2b. +See src/Encryption.cpp, src/Signature.cpp and src/Hash.cpp. +Also check Scheme.md for packet construction. # Limits Only 65kb of data can be used for each `add`. You can add more data by using `add` several times. User and group name can't be longer than 255 characters. @@ -21,3 +22,5 @@ for a period of time since the request can be received before we have got the re This can happen because we dont verify all peers get the data so by the time one peer gets the data, they could have got other data from us (depends on network routing, network speed etc). Out of order requests also allows the network to operate faster. 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 diff --git a/Scheme.md b/Scheme.md new file mode 100644 index 0000000..61fdce5 --- /dev/null +++ b/Scheme.md @@ -0,0 +1,18 @@ +# Create +Packet + Header + packet structure version + timestamp + creator public key + Body (Encrypted) + creator name + name +# Add +Packet + creator public key + Content (Signed with creator private key, verify with creator public key) + Header + packet structure version + timestamp + Body (Encrypted with node encryption key) + data diff --git a/include/Database.hpp b/include/Database.hpp index e8b35bb..d160133 100644 --- a/include/Database.hpp +++ b/include/Database.hpp @@ -2,17 +2,110 @@ #include "types.hpp" #include "Key.hpp" -#include "StagedObject.hpp" #include "DataView.hpp" #include "DatabaseStorage.hpp" +#include "Hash.hpp" +#include "utils.hpp" +#include "StagedObject.hpp" +#include "Signature.hpp" #include <opendht/dhtrunner.h> #include <vector> #include <ntp/NtpClient.hpp> #include <boost/filesystem/path.hpp> +#include <stdexcept> namespace odhtdb { class LocalUser; + class Group; + + class CommitCreateException : public std::runtime_error + { + public: + CommitCreateException(const std::string &errMsg) : std::runtime_error(errMsg) {} + }; + + class CommitAddException : public std::runtime_error + { + public: + CommitAddException(const std::string &errMsg) : std::runtime_error(errMsg) {} + }; + + class DatabaseCreateException : public std::runtime_error + { + public: + DatabaseCreateException(const std::string &errMsg) : std::runtime_error(errMsg) {} + }; + + class DatabaseAddException : public std::runtime_error + { + public: + DatabaseAddException(const std::string &errMsg) : std::runtime_error(errMsg) {} + }; + + struct DatabaseCreateRequest + { + DISABLE_COPY(DatabaseCreateRequest) + + u64 timestamp; // In microseconds + Group *creatorGroup; + std::string name; + + DatabaseCreateRequest(u64 _timestamp, Group *_creatorGroup, std::string &&_name) : + timestamp(_timestamp), + creatorGroup(_creatorGroup), + name(std::move(_name)) + { + + } + + DatabaseCreateRequest(DatabaseCreateRequest &&other) + { + timestamp = other.timestamp; + creatorGroup = other.creatorGroup; + name = std::move(other.name); + + other.timestamp = 0; + other.creatorGroup = nullptr; + } + }; + + struct DatabaseAddRequest + { + DISABLE_COPY(DatabaseAddRequest) + + u16 packetStructureVersion; + u64 timestamp; // In microseconds + Signature::PublicKey creatorPublicKey; + DataView data; + + DatabaseAddRequest(u16 _packetStructureVersion, u64 _timestamp, Signature::PublicKey &&_creatorPublicKey, DataView &_data) : + packetStructureVersion(_packetStructureVersion), + timestamp(_timestamp), + creatorPublicKey(std::move(_creatorPublicKey)), + data(_data) + { + + } + + ~DatabaseAddRequest() + { + free(data.data); + data = DataView(); + } + }; + + class DatabaseCreateResponse + { + public: + DatabaseCreateResponse(const std::shared_ptr<char*> &key, const std::shared_ptr<Hash> &hash); + + const std::shared_ptr<char*> getNodeEncryptionKey() const; + const std::shared_ptr<Hash> getRequestHash() const; + private: + std::shared_ptr<char*> key; + std::shared_ptr<Hash> hash; + }; class Database { @@ -20,22 +113,26 @@ namespace odhtdb Database(const char *bootstrapNodeAddr, u16 port, boost::filesystem::path storageDir); ~Database(); - void seed(); - void create(LocalUser *owner, const Key &key); - void add(LocalUser *owner, const Key &key, DataView data); + void seed(const std::shared_ptr<Hash> hash, const std::shared_ptr<char*> encryptionKey); + // Throws DatabaseCreateException on failure. + std::unique_ptr<DatabaseCreateResponse> create(const LocalUser *owner, const std::string &name); + // Throws DatabaseAddException on failure + void add(const LocalUser *owner, const Key &key, DataView data); void commit(); private: - void commitStagedCreateObject(const StagedCreateObject &stagedObject); - void commitStagedAddObject(const StagedAddObject &stagedObject); + // Throws CommitCreateException on failure + void commitStagedCreateObject(const std::unique_ptr<StagedCreateObject> &stagedObject); + // Throws CommitAddException on failure + void commitStagedAddObject(const DataView &stagedObject); ntp::NtpTimestamp getSyncedTimestampUtc() const; - StagedCreateObject deserializeCreateRequest(const std::shared_ptr<dht::Value> &value); - StagedAddObject deserializeAddRequest(const std::shared_ptr<dht::Value> &value); - bool listenCreateData(std::shared_ptr<dht::Value> value); - bool listenAddData(std::shared_ptr<dht::Value> value); + DatabaseCreateRequest deserializeCreateRequest(const std::shared_ptr<dht::Value> &value, const Hash &hash, const std::shared_ptr<char*> encryptionKey); + DatabaseAddRequest deserializeAddRequest(const std::shared_ptr<dht::Value> &value, const Hash &hash, 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 &hash, const std::shared_ptr<char*> encryptionKey); private: dht::DhtRunner node; - std::vector<StagedCreateObject> stagedCreateObjects; - std::vector<StagedAddObject> stagedAddObjects; + std::vector<std::unique_ptr<StagedCreateObject>> stagedCreateObjects; + std::vector<std::unique_ptr<DataView>> stagedAddObjects; DatabaseStorage databaseStorage; }; } diff --git a/include/DatabaseStorage.hpp b/include/DatabaseStorage.hpp index 863c5d9..6f251d1 100644 --- a/include/DatabaseStorage.hpp +++ b/include/DatabaseStorage.hpp @@ -1,8 +1,9 @@ #pragma once -#include "Key.hpp" +#include "Hash.hpp" #include "DataView.hpp" #include "Signature.hpp" +#include "Encryption.hpp" #include <vector> #include <stdexcept> @@ -28,6 +29,8 @@ namespace odhtdb u64 timestamp; // In microseconds std::vector<Group*> groups; std::vector<DatabaseStorageObject> objects; + u8 *createData; + usize createDataSize; }; class DatabaseStorageAlreadyExists : public std::runtime_error @@ -42,16 +45,19 @@ namespace odhtdb DatabaseStorageNotFound(const std::string &errMsg) : std::runtime_error(errMsg) {} }; - using DatabaseStorageMap = KeyMap<DatabaseStorageObjectList*>; + using DatabaseStorageMap = MapHashKey<DatabaseStorageObjectList*>; class DatabaseStorage { public: - // Throws DatabaseStorageAlreadyExists if data with key already exists - void createStorage(const Key &key, std::vector<Group*> &&groups, u64 timestamp); + // Throws DatabaseStorageAlreadyExists if data with hash already exists + void createStorage(const Hash &hash, Group *creatorGroup, u64 timestamp, const u8 *data, usize dataSize); - // Throws DatabaseStorageNotFound if data with key does not exist - void appendStorage(const Key &key, DataView &data, u64 timestamp, const Signature::PublicKey &creatorPublicKey); + // Throws DatabaseStorageNotFound if data with hash does not exist + void appendStorage(const Hash &hash, DataView &data, u64 timestamp, const Signature::PublicKey &creatorPublicKey); + + // Returns nullptr if not storage with provided hash exists + const DatabaseStorageObjectList* getStorage(const Hash &hash) const; private: DatabaseStorageMap storageMap; }; diff --git a/include/DhtKey.hpp b/include/DhtKey.hpp new file mode 100644 index 0000000..7c30ee3 --- /dev/null +++ b/include/DhtKey.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "Hash.hpp" +#include <opendht/infohash.h> + +namespace odhtdb +{ + class DhtKey + { + public: + DhtKey(const Hash &key); + + const dht::InfoHash& getNewDataListenerKey(); + const dht::InfoHash& getRequestOldDataKey(); + private: + dht::InfoHash infoHash; + unsigned char firstByteOriginalValue; + }; +} diff --git a/include/Encryption.hpp b/include/Encryption.hpp index b70687d..b2afe49 100644 --- a/include/Encryption.hpp +++ b/include/Encryption.hpp @@ -1,29 +1,61 @@ #pragma once /* - * Encrypts/decrypts data using xchacha20 + * Encrypts/decrypts data using xchacha20-poly1305 ietf */ +#include "DataView.hpp" +#include "utils.hpp" #include <string> +#include <stdexcept> namespace odhtdb { const int NONCE_BYTE_SIZE = 24; + const int KEY_BYTE_SIZE = 32; - struct EncryptedData + class EncryptionException : public std::runtime_error { - char nonce[NONCE_BYTE_SIZE]; - std::string data; + public: + EncryptionException(const std::string &errMsg) : std::runtime_error(errMsg) {} }; - using EncryptionKey = char[32]; - - // Stores randomly generated encryption key in @output - void generateEncryptionKey(EncryptionKey *output); + class DecryptionException : public std::runtime_error + { + public: + DecryptionException(const std::string &errMsg) : std::runtime_error(errMsg) {} + }; - // Returns 0 on success, storing encrypted data in @output - int encrypt(EncryptedData *output, const EncryptionKey *key, const void *data, size_t dataSize); + class Encryption + { + DISABLE_COPY(Encryption) + public: + // Throws EncryptionException on failure (or std::bad_alloc on failed memory allocation) + Encryption(const DataView &data) : Encryption(data, DataView()) {} + Encryption(const DataView &data, const DataView &additionalData); + ~Encryption(); + + DataView getKey() const; + DataView getNonce() const; + DataView getCipherText() const; + private: + unsigned char key[KEY_BYTE_SIZE]; + unsigned char nonce[NONCE_BYTE_SIZE]; + unsigned char *cipherText; + unsigned long long cipherTextLength; + }; - // Returns 0 on success, storing decrypted data in @output - int decrypt(std::string *output, const EncryptionKey *key, const EncryptedData *encryptedData); + class Decryption + { + DISABLE_COPY(Decryption) + public: + // Throws DecryptionException on failure + Decryption(const DataView &data, const DataView &nonce, const DataView &key); + ~Decryption(); + + DataView getDecryptedText() const; + private: + unsigned char *decryptedText; + unsigned long long decryptedTextLength; + }; } diff --git a/include/Group.hpp b/include/Group.hpp index c909728..a8dcf83 100644 --- a/include/Group.hpp +++ b/include/Group.hpp @@ -24,12 +24,12 @@ namespace odhtdb Group(const std::string &name); ~Group(); - void addUser(User *user); + void addUser(const User *user); const std::string& getName() const; - const std::vector<User*>& getUsers() const; + const std::vector<const User*>& getUsers() const; private: std::string name; - std::vector<User*> users; + std::vector<const User*> users; }; -}
\ No newline at end of file +} diff --git a/include/Hash.hpp b/include/Hash.hpp new file mode 100644 index 0000000..d7c90b0 --- /dev/null +++ b/include/Hash.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "utils.hpp" +#include <sodium/crypto_generichash_blake2b.h> +#include <stdexcept> +#include <unordered_map> + +namespace odhtdb +{ + const int HASH_BYTE_SIZE = 32; + + class HashException : public std::runtime_error + { + public: + HashException(const std::string &errMsg) : std::runtime_error(errMsg) {} + }; + + class Hash + { + public: + Hash(); + // Throws HashException on failure + Hash(const void *input, const size_t inputSize); + + void* getData() const { return (void*)data; } + size_t getSize() const { return HASH_BYTE_SIZE; } + + size_t operator()() const; + bool operator==(const Hash &other) const; + + std::string toString() const; + private: + char data[HASH_BYTE_SIZE]; + }; + + struct HashHasher + { + size_t operator()(const Hash &hash) const + { + return hash(); + } + }; + + template <typename ValueType> + using MapHashKey = std::unordered_map<Hash, ValueType, HashHasher>; +} diff --git a/include/Key.hpp b/include/Key.hpp index f7a600b..18971d1 100644 --- a/include/Key.hpp +++ b/include/Key.hpp @@ -1,7 +1,6 @@ #pragma once #include <opendht/infohash.h> -#include <unordered_map> namespace odhtdb { @@ -13,32 +12,4 @@ namespace odhtdb dht::InfoHash hashedKey; }; - - // Source: https://stackoverflow.com/a/11414104 (public license) - static unsigned int fnvHash(const unsigned char *key, int len) - { - unsigned int h = 2166136261; - for (int i = 0; i < len; i++) - h = (h * 16777619) ^ key[i]; - return h; - } - - struct KeyHash - { - size_t operator()(const Key &key) const - { - return fnvHash(key.hashedKey.data(), key.hashedKey.size()); - } - }; - - struct KeyCompare - { - bool operator()(const Key &lhs, const Key &rhs) const - { - return lhs.hashedKey == rhs.hashedKey; - } - }; - - template <typename ValueType> - using KeyMap = std::unordered_map<Key, ValueType, KeyHash, KeyCompare>; } diff --git a/include/StagedObject.hpp b/include/StagedObject.hpp index fccf4f6..a75664e 100644 --- a/include/StagedObject.hpp +++ b/include/StagedObject.hpp @@ -1,40 +1,22 @@ #pragma once -#include "Key.hpp" -#include "types.hpp" +#include "utils.hpp" +#include "Hash.hpp" #include "DataView.hpp" -#include "Signature.hpp" namespace odhtdb { - class Group; - struct StagedCreateObject { - Key key; - Group *primaryAdminGroup; - u64 timestamp; // In microseconds - - StagedCreateObject() : key(), primaryAdminGroup(nullptr), timestamp(0) {} - StagedCreateObject(const Key &_key, Group *_primaryAdminGroup, u64 _timestamp) : - key(_key), primaryAdminGroup(_primaryAdminGroup), timestamp(_timestamp) - { - - } - }; - - struct StagedAddObject - { - Key key; - std::unique_ptr<std::string> data; - u64 timestamp; // In microseconds - Signature::PublicKey creatorPublicKey; - - StagedAddObject() : key(), data(), timestamp(0), creatorPublicKey(Signature::PublicKey::ZERO) {} - StagedAddObject(const Key &_key, std::unique_ptr<std::string> &&_data, u64 _timestamp, const Signature::PublicKey &_creatorPublicKey) : - key(_key), data(std::move(_data)), timestamp(_timestamp), creatorPublicKey(_creatorPublicKey) + DISABLE_COPY(StagedCreateObject) + DataView encryptedBody; + std::shared_ptr<Hash> requestKey; + + StagedCreateObject(DataView &_encryptedBody, const std::shared_ptr<Hash> &_requestKey) : + encryptedBody(_encryptedBody), + requestKey(_requestKey) { - + } }; } diff --git a/include/bin2hex.hpp b/include/bin2hex.hpp new file mode 100644 index 0000000..72b57c1 --- /dev/null +++ b/include/bin2hex.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include <string> + +namespace odhtdb +{ + static const char HEX_TABLE[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + + static std::string bin2hex(const char *data, size_t dataSize) + { + std::string result; + result.resize(dataSize * 2); + + for(int i = 0; i < dataSize; ++i) + { + char c = data[i]; + result[i * 2 + 0] = HEX_TABLE[(c & 0xF0) >> 4]; + result[i * 2 + 1] = HEX_TABLE[(c & 0x0F)]; + } + + return result; + } +} diff --git a/src/Database.cpp b/src/Database.cpp index 90e83c1..bcf5580 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -2,9 +2,12 @@ #include "../include/Group.hpp" #include "../include/LocalUser.hpp" #include "../include/RemoteUser.hpp" +#include "../include/Encryption.hpp" +#include "../include/DhtKey.hpp" +#include "../include/bin2hex.hpp" #include <opendht.h> #include <fmt/format.h> -#include <sodium/crypto_box_curve25519xchacha20poly1305.h> +#include <sodium/randombytes.h> #include <thread> #include <chrono> #include <sibs/SafeSerializer.hpp> @@ -27,9 +30,54 @@ const int OPENDHT_INFOHASH_LEN = 20; namespace odhtdb { + const u16 DATABASE_CREATE_PACKET_STRUCTURE_VERSION = 0; + const u16 DATABASE_ADD_PACKET_STRUCTURE_VERSION = 0; + + DataView combine(sibs::SafeSerializer &headerSerializer, const Encryption &encryptedData) + { + usize allocationSize = headerSerializer.getBuffer().size() + encryptedData.getNonce().size + encryptedData.getCipherText().size; + char *result = new char[allocationSize]; + memcpy(result, headerSerializer.getBuffer().data(), headerSerializer.getBuffer().size()); + memcpy(result + headerSerializer.getBuffer().size(), encryptedData.getNonce().data, encryptedData.getNonce().size); + memcpy(result + headerSerializer.getBuffer().size() + encryptedData.getNonce().size, encryptedData.getCipherText().data, encryptedData.getCipherText().size); + return DataView(result, allocationSize); + } + + DatabaseCreateResponse::DatabaseCreateResponse(const shared_ptr<char*> &_key, const shared_ptr<Hash> &_hash) : + key(move(_key)), + hash(_hash) + { + + } + + const shared_ptr<char*> DatabaseCreateResponse::getNodeEncryptionKey() const + { + return key; + } + + const shared_ptr<Hash> DatabaseCreateResponse::getRequestHash() const + { + return hash; + } + Database::Database(const char *bootstrapNodeAddr, u16 port, boost::filesystem::path storageDir) { - node.run(port, dht::crypto::generateIdentity(), true); + // TODO: Cache this in storage. It takes pretty long time to generate new identity + auto identity = dht::crypto::generateIdentity(); + node.run(port , { + /*.dht_config = */{ + /*.node_config = */{ + /*.node_id = */{}, + /*.network = */0, + /*.is_bootstrap = */false, + /*.maintain_storage*/false + }, + /*.id = */identity + }, + /*.threaded = */true, + /*.proxy_server = */"", + /*.push_node_id = */"" + }); fmt::MemoryWriter portStr; portStr << port; node.bootstrap(bootstrapNodeAddr, portStr.c_str()); @@ -73,33 +121,139 @@ namespace odhtdb node.join(); } - void Database::seed() + void Database::seed(const shared_ptr<Hash> hash, const shared_ptr<char*> encryptionKey) { // 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) + + printf("Seeding key: %s\n", hash->toString().c_str()); + DhtKey dhtKey(*hash); + + node.listen(dhtKey.getNewDataListenerKey(), [this, hash, encryptionKey](const shared_ptr<Value> &value) + { + return listenAddData(value, *hash, encryptionKey); + }); + + 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) + { + const Hash requestHash(value->data.data(), value->data.size()); + if(requestHash == *hash) + return listenCreateData(value, requestHash, encryptionKey); + else + return listenAddData(value, requestHash, encryptionKey); + }); + + // 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) + { + printf("Request: Got request to send old data\n"); + try + { + sibs::SafeDeserializer deserializer(value->data.data(), value->data.size()); + u64 dataStartTimestamp = deserializer.extract<u64>(); + u8 requestResponseKey[OPENDHT_INFOHASH_LEN]; + deserializer.extract(requestResponseKey, OPENDHT_INFOHASH_LEN); + + auto requestedData = databaseStorage.getStorage(*hash); + if(!requestedData) + { + fprintf(stderr, "Warning: No data found for hash %s, unable to serve peer\n", hash->toString().c_str()); + return true; + } + + if(dataStartTimestamp == 0) + { + printf("Request: Sent create packet to requesting peer\n"); + node.put(InfoHash(requestResponseKey, OPENDHT_INFOHASH_LEN), Value(requestedData->createData, requestedData->createDataSize), [](bool ok) + { + if(!ok) + fprintf(stderr, "Failed to put response for old data\n"); + }); + } + else + { + assert(false); + printf("TODO: Send 'add' packets to requesting remote peer\n"); + } + } + catch (sibs::DeserializeException &e) + { + fprintf(stderr, "Warning: Failed to deserialize 'get old data' request: %s\n", e.what()); + } + return true; + }); + + sibs::SafeSerializer serializer; + serializer.add((u64)0); // Timestamp in microseconds, fetch data newer than this + serializer.add(responseKey, OPENDHT_INFOHASH_LEN); + node.put(dhtKey.getRequestOldDataKey(), Value(serializer.getBuffer().data(), serializer.getBuffer().size()), [](bool ok) + { + if(!ok) + fprintf(stderr, "Failed to put request to get old data\n"); + }); - using std::placeholders::_1; - node.listen(CREATE_DATA_HASH, bind(&Database::listenCreateData, this, _1)); - node.listen(ADD_DATA_HASH, bind(&Database::listenAddData, this, _1)); + //node.listen(CREATE_DATA_HASH, bind(&Database::listenCreateData, this, _1)); + //node.listen(ADD_DATA_HASH, bind(&Database::listenAddData, this, _1)); } - void Database::create(LocalUser *owner, const Key &key) + unique_ptr<DatabaseCreateResponse> Database::create(const LocalUser *owner, const std::string &name) { - Group *primaryAdminGroup = new Group("admin"); - primaryAdminGroup->addUser(owner); + // Header + sibs::SafeSerializer serializer; + serializer.add(DATABASE_CREATE_PACKET_STRUCTURE_VERSION); // Packet structure version // TODO: Append fractions to get real microseconds time - u64 timeMicroseconds = ((u64)getSyncedTimestampUtc().seconds) * 1000000ull; - stagedCreateObjects.emplace_back(StagedCreateObject(key, primaryAdminGroup, timeMicroseconds)); + u64 timestampMicroseconds = ((u64)getSyncedTimestampUtc().seconds) * 1000000ull; + serializer.add(timestampMicroseconds); + serializer.add((u8*)owner->getPublicKey().getData(), PUBLIC_KEY_NUM_BYTES); + + // Encrypted body + sibs::SafeSerializer encryptedSerializer; + assert(owner->getName().size() <= 255); + encryptedSerializer.add((u8)owner->getName().size()); + encryptedSerializer.add((u8*)owner->getName().data(), owner->getName().size()); + assert(name.size() <= 255); + encryptedSerializer.add((u8)name.size()); + encryptedSerializer.add((u8*)name.data(), name.size()); + + try + { + Encryption encryptedBody(DataView(encryptedSerializer.getBuffer().data(), encryptedSerializer.getBuffer().size())); + DataView requestData = combine(serializer, encryptedBody); + shared_ptr<Hash> hashRequestKey = make_shared<Hash>(requestData.data, requestData.size); + auto adminGroup = new Group("administrator"); + adminGroup->addUser(owner); + databaseStorage.createStorage(*hashRequestKey, adminGroup, timestampMicroseconds, (const u8*)requestData.data, requestData.size); + stagedCreateObjects.emplace_back(make_unique<StagedCreateObject>(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>(make_shared<char*>(key), hashRequestKey); + } + catch (EncryptionException &e) + { + throw DatabaseCreateException("Failed to encrypt data for 'create' request"); + } } - void Database::add(LocalUser *owner, const Key &key, DataView data) + void Database::add(const LocalUser *owner, const Key &key, DataView data) { +#if 0 + if(nodeEncryptionKeys.find(key) == nodeEncryptionKeys.end()) + throw DatabaseAddException("Data for key needs to be created before data can be appended to it"); + unique_ptr<string> signedData = make_unique<string>(owner->getPrivateKey().sign(data)); // TODO: Append fractions to get real microseconds time u64 timeMicroseconds = ((u64)getSyncedTimestampUtc().seconds) * 1000000ull; stagedAddObjects.emplace_back(StagedAddObject(key, move(signedData), timeMicroseconds, owner->getPublicKey())); +#endif } void Database::commit() @@ -107,86 +261,71 @@ namespace odhtdb // TODO: Combine staged objects into one object for efficiency. // TODO: Add rollback - printf("Num objects to create: %d\n", stagedCreateObjects.size()); - for(StagedCreateObject &stagedObject : stagedCreateObjects) + try { - commitStagedCreateObject(stagedObject); - delete stagedObject.primaryAdminGroup; + printf("Num objects to create: %zu\n", stagedCreateObjects.size()); + for(const auto &stagedObject : stagedCreateObjects) + { + commitStagedCreateObject(stagedObject); + } + } + catch (exception &e) + { + fprintf(stderr, "Error: Failed to commit, reason: %s\n", e.what()); + } + + for(const auto &stagedObject : stagedCreateObjects) + { + free(stagedObject->encryptedBody.data); } stagedCreateObjects.clear(); - +#if 0 printf("Num objects to add: %d\n", stagedAddObjects.size()); for(StagedAddObject &stagedObject : stagedAddObjects) { commitStagedAddObject(stagedObject); } stagedAddObjects.clear(); - +#endif // TODO: Add node.listen here to get notified when remote peers got the commit, then we can say we can return } - // TODO: If same key already exists, fail the operation. - // Security issue: A malicious remote peer (or routing peer) could listen to this create request and build their own - // create request using same key, to steal ownership of the key. - // Possible solution: If odhtdb is for example used to build a chat application, then the key could be the chat channel id - // which could be created by hashing channel generated id and ownership information. - // Remote peers would then not be able to steal ownership of the key since hash of ownership data has to match the key. - // The key (channel id + ownership info) could then be shared with friends and they can use the key to join your channel. - void Database::commitStagedCreateObject(const StagedCreateObject &stagedObject) + void Database::commitStagedCreateObject(const unique_ptr<StagedCreateObject> &stagedObject) { - // TODO: Use (ed25519 or poly1305) and curve25519 - // TODO: Implement gas and price (refill when serving content (seeding) or by waiting. This is done to prevent spamming and bandwidth leeching) - sibs::SafeSerializer serializer; - assert(stagedObject.key.hashedKey.size() == OPENDHT_INFOHASH_LEN); - serializer.add(stagedObject.key.hashedKey.data(), stagedObject.key.hashedKey.size()); - serializer.add(stagedObject.timestamp); - serializer.add((u8)stagedObject.primaryAdminGroup->getName().size()); - serializer.add((u8*)stagedObject.primaryAdminGroup->getName().data(), stagedObject.primaryAdminGroup->getName().size()); - assert(stagedObject.primaryAdminGroup->getUsers().size() <= 255); - serializer.add((u8)stagedObject.primaryAdminGroup->getUsers().size()); - for(User *user : stagedObject.primaryAdminGroup->getUsers()) - { - serializer.add((u8*)user->getPublicKey().getData(), PUBLIC_KEY_NUM_BYTES); - serializer.add((u8)user->getName().size()); - serializer.add((u8*)user->getName().data(), user->getName().size()); - } - - // TODO: Verify if serializer buffer needs to survive longer than this scope - Value createDataValue(serializer.getBuffer().data(), serializer.getBuffer().size()); - node.put(CREATE_DATA_HASH, move(createDataValue), [](bool ok) + DhtKey dhtKey(*stagedObject->requestKey); + Value createDataValue((u8*)stagedObject->encryptedBody.data, stagedObject->encryptedBody.size); + node.put(dhtKey.getNewDataListenerKey(), move(createDataValue), [](bool ok) { // TODO: Handle failure to put data if(!ok) fprintf(stderr, "Failed to put: %s, what to do?\n", "commitStagedCreateObject"); }/* TODO: How to make this work?, time_point(), false*/); - - // Post data for listeners of this key - /* - Value putKeyValue(serializer.getBuffer().data() + OPENDHT_INFOHASH_LEN, serializer.getBuffer().size() - OPENDHT_INFOHASH_LEN); - node.put(stagedObject.key.hashedKey, move(putKeyValue), [](bool ok) - { - // TODO: Handle failure to put data - if(!ok) - fprintf(stderr, "Failed to put for listeners: %s, what to do?\n", "commitStagedCreateObject"); - }); - */ } - void Database::commitStagedAddObject(const StagedAddObject &stagedObject) + void Database::commitStagedAddObject(const DataView &stagedObject) { - // TODO: Use (ed25519 or poly1305) and curve25519 +#if 0 // TODO: Implement gas and price (refill when serving content (seeding) or by waiting. This is done to prevent spamming and bandwidth leeching) - sibs::SafeSerializer serializer; + sibs::SafeSerializer headerSerializer; assert(stagedObject.key.hashedKey.size() == OPENDHT_INFOHASH_LEN); - serializer.add(stagedObject.key.hashedKey.data(), OPENDHT_INFOHASH_LEN); - serializer.add(stagedObject.timestamp); - serializer.add((u8*)stagedObject.creatorPublicKey.getData(), PUBLIC_KEY_NUM_BYTES); + headerSerializer.add(stagedObject.key.hashedKey.data(), OPENDHT_INFOHASH_LEN); + headerSerializer.add(stagedObject.timestamp); + + sibs::SafeSerializer bodySerializer; + bodySerializer.add((u8*)stagedObject.creatorPublicKey.getData(), PUBLIC_KEY_NUM_BYTES); assert(stagedObject.data->size() < 0xFFFF - 120); - serializer.add((u16)stagedObject.data->size()); - serializer.add((u8*)stagedObject.data->data(), stagedObject.data->size()); + bodySerializer.add((u16)stagedObject.data->size()); + bodySerializer.add((u8*)stagedObject.data->data(), stagedObject.data->size()); + + EncryptedData encryptedData; + if(encrypt(&encryptedData, (EncryptionKey*)nodeEncryptionKeys[stagedObject.key], bodySerializer.getBuffer().data(), bodySerializer.getBuffer().size()) < 0) + throw CommitAddException("Failed to encrypt staged add object"); + + Blob serializedData; + combine(&serializedData, headerSerializer, encryptedData); // TODO: Verify if serializer buffer needs to survive longer than this scope - Value addDataValue(serializer.getBuffer().data(), serializer.getBuffer().size()); + Value addDataValue(move(serializedData)); node.put(ADD_DATA_HASH, move(addDataValue), [](bool ok) { // TODO: Handle failure to put data @@ -204,6 +343,7 @@ namespace odhtdb fprintf(stderr, "Failed to put for listeners: %s, what to do?\n", "commitStagedAddObject"); }); */ +#endif } ntp::NtpTimestamp Database::getSyncedTimestampUtc() const @@ -215,44 +355,59 @@ namespace odhtdb return timestamp; } - StagedCreateObject Database::deserializeCreateRequest(const std::shared_ptr<dht::Value> &value) + DatabaseCreateRequest Database::deserializeCreateRequest(const std::shared_ptr<dht::Value> &value, const Hash &hash, const shared_ptr<char*> encryptionKey) { - StagedCreateObject result; - sibs::SafeDeserializer deserializer(value->data.data(), value->data.size()); - u8 entryKeyRaw[OPENDHT_INFOHASH_LEN]; - deserializer.extract(entryKeyRaw, OPENDHT_INFOHASH_LEN); - result.key.hashedKey = InfoHash(entryKeyRaw, OPENDHT_INFOHASH_LEN); - result.timestamp = deserializer.extract<u64>(); - - u8 adminGroupNameSize = deserializer.extract<u8>(); - string adminGroupName; - adminGroupName.resize(adminGroupNameSize); - deserializer.extract((u8*)&adminGroupName[0], adminGroupNameSize); - result.primaryAdminGroup = new Group(adminGroupName); - - u8 numUsers = deserializer.extract<u8>(); - for(int i = 0; i < numUsers; ++i) + u16 packetStructureVersion = deserializer.extract<u16>(); + if(packetStructureVersion != DATABASE_CREATE_PACKET_STRUCTURE_VERSION) { - char userPublicKeyRaw[PUBLIC_KEY_NUM_BYTES]; - deserializer.extract((u8*)userPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); - Signature::PublicKey userPublicKey(userPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); - - u8 userNameSize = deserializer.extract<u8>(); - string userName; - userName.resize(userNameSize); - deserializer.extract((u8*)&userName[0], userNameSize); - - RemoteUser *user = RemoteUser::create(userPublicKey, userName); - result.primaryAdminGroup->addUser(user); + string errMsg = "Received 'create' request with packet structure version "; + errMsg += to_string(packetStructureVersion); + errMsg += ", but our packet structure version is "; + errMsg += to_string(DATABASE_CREATE_PACKET_STRUCTURE_VERSION); + throw sibs::DeserializeException(errMsg); } - // NOTE: There might be more data in deserializer, but we can ignore those; we already got all data we need - return result; + u64 creationDate = deserializer.extract<u64>(); + // TODO: Append fractions to get real microseconds time + u64 timestampMicroseconds = ((u64)getSyncedTimestampUtc().seconds) * 1000000ull; + if(creationDate > timestampMicroseconds) + throw sibs::DeserializeException("Packet is from the future"); + + char creatorPublicKeyRaw[PUBLIC_KEY_NUM_BYTES]; + deserializer.extract((u8*)creatorPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); + Signature::PublicKey userPublicKey(creatorPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); + + if(deserializer.getSize() < NONCE_BYTE_SIZE) + throw sibs::DeserializeException("Unsigned encrypted body is too small (unable to extract nonce)"); + + auto adminGroup = new Group("administrator"); + auto creatorUser = RemoteUser::create(userPublicKey, ""); // Username is encrypted, we dont know it... + adminGroup->addUser(creatorUser); + databaseStorage.createStorage(hash, adminGroup, creationDate, value->data.data(), value->data.size()); + + u8 nonce[NONCE_BYTE_SIZE]; + deserializer.extract(nonce, NONCE_BYTE_SIZE); + + DataView dataToDecrypt((void*)deserializer.getBuffer(), deserializer.getSize()); + Decryption decryptedBody(dataToDecrypt, DataView(nonce, NONCE_BYTE_SIZE), DataView(*encryptionKey, KEY_BYTE_SIZE)); + sibs::SafeDeserializer bodyDeserializer((const u8*)decryptedBody.getDecryptedText().data, decryptedBody.getDecryptedText().size); + u8 creatorNameLength = bodyDeserializer.extract<u8>(); + string creatorName; + creatorName.resize(creatorNameLength); + bodyDeserializer.extract((u8*)&creatorName[0], creatorNameLength); + + u8 nameLength = bodyDeserializer.extract<u8>(); + string name; + name.resize(nameLength); + bodyDeserializer.extract((u8*)&name[0], nameLength); + + return { creationDate, adminGroup, move(name) }; } - StagedAddObject Database::deserializeAddRequest(const std::shared_ptr<dht::Value> &value) + DatabaseAddRequest Database::deserializeAddRequest(const std::shared_ptr<dht::Value> &value, const Hash &hash, const shared_ptr<char*> encryptionKey) { + /* StagedAddObject result; sibs::SafeDeserializer deserializer(value->data.data(), value->data.size()); @@ -277,47 +432,41 @@ namespace odhtdb result.data = make_unique<string>(creatorPublicKey.unsign(DataView((void*)signedData.data(), signedData.size()))); return result; + */ + Signature::PublicKey publicKey(nullptr, 0); + DataView d; + return { 0, 0, move(publicKey), d }; } - bool Database::listenCreateData(std::shared_ptr<dht::Value> value) + bool Database::listenCreateData(std::shared_ptr<dht::Value> value, const Hash &hash, const shared_ptr<char*> encryptionKey) { printf("Got create data\n"); try { - // TODO: Verify createObject timestamp is not in the future - StagedCreateObject createObject = deserializeCreateRequest(value); - databaseStorage.createStorage(createObject.key, { createObject.primaryAdminGroup }, createObject.timestamp); - } - catch (sibs::DeserializeException &e) - { - fprintf(stderr, "Warning: Failed to deserialize 'create' request: %s\n", e.what()); + if(databaseStorage.getStorage(hash)) + throw DatabaseStorageAlreadyExists("Create request hash is equal to hash already in storage (duplicate data?)"); + DatabaseCreateRequest createObject = deserializeCreateRequest(value, hash, encryptionKey); + printf("Got create object, name: %s\n", createObject.name.c_str()); } - catch (DatabaseStorageAlreadyExists &e) + catch (exception &e) { fprintf(stderr, "Warning: Failed to deserialize 'create' request: %s\n", e.what()); } return true; } - bool Database::listenAddData(std::shared_ptr<dht::Value> value) + bool Database::listenAddData(std::shared_ptr<dht::Value> value, const Hash &hash, const shared_ptr<char*> encryptionKey) { printf("Got add data\n"); try { + if(databaseStorage.getStorage(hash)) + throw DatabaseStorageAlreadyExists("Add request hash is equal to hash already in storage (duplicate data?)"); // TODO: Verify createObject timestamp is not in the future - StagedAddObject addObject = deserializeAddRequest(value); - DataView data((void*)addObject.data->data(), addObject.data->size()); - databaseStorage.appendStorage(addObject.key, data, addObject.timestamp, addObject.creatorPublicKey); - } - catch (sibs::DeserializeException &e) - { - fprintf(stderr, "Warning: Failed to deserialize 'add' request: %s\n", e.what()); - } - catch (DatabaseStorageNotFound &e) - { - fprintf(stderr, "Warning: Failed to deserialize 'add' request: %s\n", e.what()); + //StagedAddObject addObject = deserializeAddRequest(value); + //DataView data((void*)addObject.data->data(), addObject.data->size()); } - catch (UnsignException &e) + catch (exception &e) { fprintf(stderr, "Warning: Failed to deserialize 'add' request: %s\n", e.what()); } diff --git a/src/DatabaseStorage.cpp b/src/DatabaseStorage.cpp index 62a2e77..25d41fb 100644 --- a/src/DatabaseStorage.cpp +++ b/src/DatabaseStorage.cpp @@ -1,35 +1,49 @@ #include "../include/DatabaseStorage.hpp" +#include <cstring> using namespace std; namespace odhtdb { - void DatabaseStorage::createStorage(const Key &key, vector<Group*> &&groups, u64 timestamp) + void DatabaseStorage::createStorage(const Hash &hash, Group *creatorGroup, u64 timestamp, const u8 *data, usize dataSize) { - if(storageMap.find(key) != storageMap.end()) + /* + if(storageMap.find(hash) != storageMap.end()) { - string errMsg = "Database storage with key "; - errMsg += key.hashedKey.toString(); + string errMsg = "Database storage with hash "; + errMsg += hash.toString(); errMsg += " already exists"; throw DatabaseStorageAlreadyExists(errMsg); } + */ DatabaseStorageObjectList *databaseStorageObjectList = new DatabaseStorageObjectList(); databaseStorageObjectList->timestamp = timestamp; - databaseStorageObjectList->groups = move(groups); - storageMap[key] = databaseStorageObjectList; + databaseStorageObjectList->groups.push_back(creatorGroup); + databaseStorageObjectList->createData = new u8[dataSize]; + memcpy(databaseStorageObjectList->createData, data, dataSize); + databaseStorageObjectList->createDataSize = dataSize; + storageMap[hash] = databaseStorageObjectList; } - void DatabaseStorage::appendStorage(const Key &key, DataView &data, u64 timestamp, const Signature::PublicKey &creatorPublicKey) + void DatabaseStorage::appendStorage(const Hash &hash, DataView &data, u64 timestamp, const Signature::PublicKey &creatorPublicKey) { - auto it = storageMap.find(key); + auto it = storageMap.find(hash); if(it == storageMap.end()) { - string errMsg = "Database storage with key "; - errMsg += key.hashedKey.toString(); - errMsg += " not found. Storage for a key needs to be created before data can be appended to it"; + string errMsg = "Database storage with hash "; + errMsg += hash.toString(); + errMsg += " not found. Storage for a hash needs to be created before data can be appended to it"; throw DatabaseStorageNotFound(errMsg); } it->second->objects.push_back({data, timestamp, creatorPublicKey}); } + + const DatabaseStorageObjectList* DatabaseStorage::getStorage(const Hash &hash) const + { + auto it = storageMap.find(hash); + if(it != storageMap.end()) + return it->second; + return nullptr; + } } diff --git a/src/DhtKey.cpp b/src/DhtKey.cpp new file mode 100644 index 0000000..ea59740 --- /dev/null +++ b/src/DhtKey.cpp @@ -0,0 +1,22 @@ +#include "../include/DhtKey.hpp" +#include "../include/types.hpp" + +namespace odhtdb +{ + DhtKey::DhtKey(const Hash &key) : infoHash((const u8*)key.getData(), key.getSize()) + { + firstByteOriginalValue = infoHash[0]; + } + + const dht::InfoHash& DhtKey::getNewDataListenerKey() + { + infoHash[0] = firstByteOriginalValue; + return infoHash; + } + + const dht::InfoHash& DhtKey::getRequestOldDataKey() + { + infoHash[0] = firstByteOriginalValue + 1; + return infoHash; + } +} diff --git a/src/Encryption.cpp b/src/Encryption.cpp index 8e87a8d..c4e6a2c 100644 --- a/src/Encryption.cpp +++ b/src/Encryption.cpp @@ -1,30 +1,60 @@ #include "../include/Encryption.hpp" -#include <sodium/crypto_stream_xchacha20.h> +#include <sodium/crypto_aead_xchacha20poly1305.h> #include <sodium/randombytes.h> #include <string> namespace odhtdb { - void generateEncryptionKey(EncryptionKey *output) + Encryption::Encryption(const DataView &data, const DataView &additionalData) { - if(!output) return; - crypto_stream_xchacha20_keygen((unsigned char*)output); + cipherText = new unsigned char[crypto_aead_xchacha20poly1305_ietf_ABYTES + data.size]; + crypto_aead_xchacha20poly1305_ietf_keygen(key); + randombytes_buf(nonce, 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"); } - int encrypt(EncryptedData *output, const EncryptionKey *key, const void *data, size_t dataSize) + Encryption::~Encryption() { - if(!output || !key) return -1; - if(dataSize == 0) return 0; - output->data.resize(dataSize); - randombytes_buf(output->nonce, NONCE_BYTE_SIZE); - return crypto_stream_xchacha20_xor((unsigned char*)&output->data[0], (const unsigned char*)data, dataSize, (const unsigned char*)output->nonce, (const unsigned char*)key); + delete[](cipherText); } - int decrypt(std::string *output, const EncryptionKey *key, const EncryptedData *encryptedData) + DataView Encryption::getKey() const { - if(!encryptedData || !key || !output) return -1; - if(encryptedData->data.empty()) return 0; - output->resize(encryptedData->data.size()); - return crypto_stream_xchacha20_xor((unsigned char*)&(*output)[0], (const unsigned char*)&encryptedData->data[0], encryptedData->data.size(), (const unsigned char*)encryptedData->nonce, (const unsigned char*)key); + return DataView((void*)key, KEY_BYTE_SIZE); + } + + DataView Encryption::getNonce() const + { + return DataView((void*)nonce, NONCE_BYTE_SIZE); + } + + DataView Encryption::getCipherText() const + { + return DataView((void*)cipherText, cipherTextLength); + } + + Decryption::Decryption(const DataView &data, const DataView &nonce, const DataView &key) + { + decryptedText = new unsigned char[data.size]; + + if(nonce.size < NONCE_BYTE_SIZE) + throw DecryptionException("Nonce is not big enough"); + + if(key.size < 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) + throw DecryptionException("Failed to decrypt data"); + } + + Decryption::~Decryption() + { + delete[](decryptedText); + } + + DataView Decryption::getDecryptedText() const + { + return DataView((void*)decryptedText, decryptedTextLength); } } diff --git a/src/Group.cpp b/src/Group.cpp index c87be1e..93a0688 100644 --- a/src/Group.cpp +++ b/src/Group.cpp @@ -14,13 +14,10 @@ namespace odhtdb Group::~Group() { - for(User *user : users) - { - delete user; - } + } - void Group::addUser(User *user) + void Group::addUser(const User *user) { users.push_back(user); } @@ -30,8 +27,8 @@ namespace odhtdb return name; } - const vector<User*>& Group::getUsers() const + const vector<const User*>& Group::getUsers() const { return users; } -}
\ No newline at end of file +} diff --git a/src/Hash.cpp b/src/Hash.cpp new file mode 100644 index 0000000..5d2f914 --- /dev/null +++ b/src/Hash.cpp @@ -0,0 +1,55 @@ +#include "../include/Hash.hpp" +#include "../include/bin2hex.hpp" +#include <sodium/crypto_generichash_blake2b.h> +#include <sodium/core.h> +#include <cstring> + +namespace odhtdb +{ + struct SodiumInitializer + { + SodiumInitializer() + { + if(sodium_init() < 0) + throw std::runtime_error("Failed to initialize libsodium"); + } + }; + + static SodiumInitializer __sodiumInitializer; + + // Source: https://stackoverflow.com/a/11414104 (public license) + static size_t fnvHash(const unsigned char *key, int len) + { + size_t h = 2166136261; + for (int i = 0; i < len; i++) + h = (h * 16777619) ^ key[i]; + return h; + } + + Hash::Hash() + { + memset(data, 0, HASH_BYTE_SIZE); + } + + Hash::Hash(const void *input, const size_t inputSize) + { + int result = crypto_generichash_blake2b((unsigned char*)data, HASH_BYTE_SIZE, (const unsigned char*)input, inputSize, nullptr, 0); + if(result < 0) + throw HashException("Failed to hash data using blake2b"); + } + + size_t Hash::operator()() const + { + return fnvHash((const unsigned char*)data, HASH_BYTE_SIZE); + } + + bool Hash::operator==(const Hash &other) const + { + return memcmp(data, other.data, HASH_BYTE_SIZE) == 0; + } + + std::string Hash::toString() const + { + return bin2hex(data, HASH_BYTE_SIZE); + } +} diff --git a/src/Signature.cpp b/src/Signature.cpp index 8d3654d..34f6190 100644 --- a/src/Signature.cpp +++ b/src/Signature.cpp @@ -42,7 +42,7 @@ namespace odhtdb string result; result.resize(signedMessage.size - SIGNED_HASH_SIZE); - if(crypto_sign_ed25519_open((unsigned char*)&result[0], nullptr, (const unsigned char*)signedMessage.data, signedMessage.size, (unsigned char*)data) != 0) + if(crypto_sign_ed25519_open((unsigned char*)&result[0], nullptr, (const unsigned char*)signedMessage.data, signedMessage.size, (unsigned char*)data) < 0) throw UnsignWrongKeyException("Message was not signed with matching private key"); return result; @@ -86,7 +86,7 @@ namespace odhtdb result.resize(crypto_sign_ed25519_BYTES + dataToSign.size); unsigned long long resultSize; - if(crypto_sign_ed25519((unsigned char*)&result[0], &resultSize, (unsigned char*)dataToSign.data, dataToSign.size, (unsigned char*)data) != 0) + if(crypto_sign_ed25519((unsigned char*)&result[0], &resultSize, (unsigned char*)dataToSign.data, dataToSign.size, (unsigned char*)data) < 0) throw DataSignException("Failed to sign data. Is private key invalid?"); if(resultSize != result.size()) @@ -105,7 +105,7 @@ namespace odhtdb KeyPair::KeyPair() { - if(crypto_sign_ed25519_keypair((unsigned char*)publicKey.data, (unsigned char*)privateKey.data) != 0) + if(crypto_sign_ed25519_keypair((unsigned char*)publicKey.data, (unsigned char*)privateKey.data) < 0) throw SignatureGenerationException("Failed to generate signature keypair"); } } diff --git a/tests/assert.hpp b/tests/assert.hpp new file mode 100644 index 0000000..86f74f2 --- /dev/null +++ b/tests/assert.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include <cstdlib> +#include <iostream> + +template <typename T> +static void assertEquals(const T &expected, const T &actual) +{ + if(expected != actual) + { + std::cerr << "Assertion failed!\nExpected: " << expected << ", actual: " << actual << std::endl; + exit(1); + } +} + +static void fail(const std::string &errMsg) +{ + fprintf(stderr, "Fail:\n%.*s\n", errMsg.size(), errMsg.c_str()); + exit(1); +} diff --git a/tests/main.cpp b/tests/main.cpp index 57b6cbd..3c7e798 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -1,7 +1,9 @@ +#include "assert.hpp" #include "../include/Database.hpp" #include "../include/Group.hpp" #include "../include/LocalUser.hpp" #include "../include/Encryption.hpp" +#include "../include/Hash.hpp" #include <vector> #include <chrono> #include <thread> @@ -10,8 +12,12 @@ using namespace std; using namespace chrono_literals; using namespace odhtdb; -#define assertEquals(expected, actual) do { if((expected) != (actual)) { fprintf(stderr, "Assert failed:\nExpected: %s, actual: %s\n", #expected, #actual); exit(1); } } while(0) -void fail(const string &errMsg) { fprintf(stderr, "Fail:\n%.*s\n", errMsg.size(), errMsg.c_str()); } +void testHash() +{ + Hash hash("odhtdb", 6); + assertEquals<string>("a7b30ec8ab92de60e551b26bb8f78d315697f84dd7f5549a143477e095ec934f", hash.toString()); + printf("hash of 'odhtdb' is: a7b30ec8ab92de60e551b26bb8f78d315697f84dd7f5549a143477e095ec934f\n"); +} void testSignData(LocalUser *localUser) { @@ -65,44 +71,40 @@ void testSignData(LocalUser *localUser) void testEncryption() { - EncryptionKey encryptionKey; - generateEncryptionKey(&encryptionKey); - const char *message = "hello, world!"; const unsigned long long messageLength = 13; - EncryptedData encryptedData; - int encrypted = encrypt(&encryptedData, &encryptionKey, message, messageLength); - if(encrypted != 0) - fail("Failed to encrypt data"); + Encryption encryption(DataView((void*)message, messageLength)); - std::string decryptedText; - int decrypted = decrypt(&decryptedText, &encryptionKey, &encryptedData); - if(decrypted != 0) - fail("Failed to decrypt data"); - - assertEquals(messageLength, decryptedText.size()); - assertEquals(0, strncmp(message, decryptedText.c_str(), messageLength)); + Decryption decryption(encryption.getCipherText(), encryption.getNonce(), encryption.getKey()); + assertEquals<unsigned long long>(messageLength, decryption.getDecryptedText().size); + assertEquals(0, strncmp(message, (const char*)decryption.getDecryptedText().data, messageLength)); } int main() { + printf("Starting tests...\n"); LocalUser *localUser = LocalUser::create(Signature::KeyPair(), "dec05eba"); testSignData(localUser); testEncryption(); - - // TODO: For tests, dont run against bootstrap.ring.cx. - // Run against a bootstrap node made only for testing which doesn't persist added data. + testHash(); + // TODO: Setup local bootstrap node for tests Database database("bootstrap.ring.cx", 4222, "storage"); - database.seed(); - - database.create(localUser, "galax.channel.latenight.chat"); + auto databaseCreateResponse = database.create(localUser, "latenight"); + /* const char *data = "hello, world!"; database.add(localUser, "galax.channel.latenight.chat", DataView{ (void*)data, strlen(data) }); database.commit(); auto start = chrono::high_resolution_clock::now(); - while(chrono::high_resolution_clock::now() - start < 5s) + while(chrono::high_resolution_clock::now() - start < 3s) + { + this_thread::sleep_for(10ms); + } + */ + database.seed(databaseCreateResponse->getRequestHash(), databaseCreateResponse->getNodeEncryptionKey()); + auto start = chrono::high_resolution_clock::now(); + while(chrono::high_resolution_clock::now() - start < 3s) { this_thread::sleep_for(10ms); } |