From eda9a7bbefc5587bf1ff895a9214f450e64575fa Mon Sep 17 00:00:00 2001 From: dec05eba Date: Mon, 5 Mar 2018 22:45:56 +0100 Subject: 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. --- src/Database.cpp | 385 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 267 insertions(+), 118 deletions(-) (limited to 'src/Database.cpp') 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 #include -#include +#include #include #include #include @@ -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 &_key, const shared_ptr &_hash) : + key(move(_key)), + hash(_hash) + { + + } + + const shared_ptr DatabaseCreateResponse::getNodeEncryptionKey() const + { + return key; + } + + const shared_ptr 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, const shared_ptr 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) + { + 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) + { + 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) + { + printf("Request: Got request to send old data\n"); + try + { + sibs::SafeDeserializer deserializer(value->data.data(), value->data.size()); + u64 dataStartTimestamp = deserializer.extract(); + 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 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 hashRequestKey = make_shared(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(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(make_shared(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 signedData = make_unique(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 &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 &value) + DatabaseCreateRequest Database::deserializeCreateRequest(const std::shared_ptr &value, const Hash &hash, const shared_ptr 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(); - - u8 adminGroupNameSize = deserializer.extract(); - string adminGroupName; - adminGroupName.resize(adminGroupNameSize); - deserializer.extract((u8*)&adminGroupName[0], adminGroupNameSize); - result.primaryAdminGroup = new Group(adminGroupName); - - u8 numUsers = deserializer.extract(); - for(int i = 0; i < numUsers; ++i) + u16 packetStructureVersion = deserializer.extract(); + 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(); - 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(); + // 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(); + string creatorName; + creatorName.resize(creatorNameLength); + bodyDeserializer.extract((u8*)&creatorName[0], creatorNameLength); + + u8 nameLength = bodyDeserializer.extract(); + string name; + name.resize(nameLength); + bodyDeserializer.extract((u8*)&name[0], nameLength); + + return { creationDate, adminGroup, move(name) }; } - StagedAddObject Database::deserializeAddRequest(const std::shared_ptr &value) + DatabaseAddRequest Database::deserializeAddRequest(const std::shared_ptr &value, const Hash &hash, const shared_ptr encryptionKey) { + /* StagedAddObject result; sibs::SafeDeserializer deserializer(value->data.data(), value->data.size()); @@ -277,47 +432,41 @@ namespace odhtdb result.data = make_unique(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 value) + bool Database::listenCreateData(std::shared_ptr value, const Hash &hash, const shared_ptr 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 value) + bool Database::listenAddData(std::shared_ptr value, const Hash &hash, const shared_ptr 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()); } -- cgit v1.2.3