From be3c931f9b2db357c0b4306ad248c968d90254a3 Mon Sep 17 00:00:00 2001 From: Aleksi Lindeman Date: Sat, 10 Feb 2018 03:38:47 +0100 Subject: Add private/public key for users --- .gitignore | 2 + README.md | 5 ++- include/Database.hpp | 5 ++- include/DatabaseStorage.hpp | 10 +++++ include/LocalUser.hpp | 20 ++++++++-- include/RemoteUser.hpp | 24 +++++++++++ include/Signature.hpp | 83 ++++++++++++++++++++++++++++++++++++++ include/User.hpp | 7 +++- odhtdb.kdev4 | 4 ++ project.conf | 3 +- src/Database.cpp | 66 +++++++++++++++++++++++++----- src/Signature.cpp | 97 +++++++++++++++++++++++++++++++++++++++++++++ tests/main.cpp | 27 ++++++++++++- 13 files changed, 330 insertions(+), 23 deletions(-) create mode 100644 include/DatabaseStorage.hpp create mode 100644 include/RemoteUser.hpp create mode 100644 include/Signature.hpp create mode 100644 odhtdb.kdev4 create mode 100644 src/Signature.cpp diff --git a/.gitignore b/.gitignore index 97420ef..38f9679 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ sibs-build/ +.kdev4/ +odhtdb.kdev4 diff --git a/README.md b/README.md index f5be103..a95c300 100644 --- a/README.md +++ b/README.md @@ -10,5 +10,6 @@ User and group name can't be longer than 255 characters. Ban nodes that spam put or get (malicious nodes). If data is routed, then the router node should first ban the malicious node so the router node is not banned if it's not malicious. But how can we know if data was routed? does opendht expose this to other nodes in some way? ## Error handling -Currently operations are executed without knowing if they succeed or not. Operations should be modified to perhaps return std::future or allow -used to pass a callback function which is called with the operation result. \ No newline at end of file +Currently operations are executed without knowing if they succeed or not. Operations should be modified to perhaps return std::future or use a callback function which is called with the operation result. +## Safely store private keys +Use Argon2 to store private keys diff --git a/include/Database.hpp b/include/Database.hpp index 68fff62..bde4d5a 100644 --- a/include/Database.hpp +++ b/include/Database.hpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace odhtdb { @@ -15,7 +16,7 @@ namespace odhtdb class Database { public: - Database(const char *bootstrapNodeAddr, u16 port); + Database(const char *bootstrapNodeAddr, u16 port, boost::filesystem::path storageDir); ~Database(); void seed(); @@ -35,4 +36,4 @@ namespace odhtdb std::vector stagedCreateObjects; std::vector stagedAddObjects; }; -} \ No newline at end of file +} diff --git a/include/DatabaseStorage.hpp b/include/DatabaseStorage.hpp new file mode 100644 index 0000000..fee6b72 --- /dev/null +++ b/include/DatabaseStorage.hpp @@ -0,0 +1,10 @@ +#pragma once + +namespace odhtdb +{ + class DatabaseStorage + { + public: + + }; +} diff --git a/include/LocalUser.hpp b/include/LocalUser.hpp index 200f30f..04f483d 100644 --- a/include/LocalUser.hpp +++ b/include/LocalUser.hpp @@ -7,11 +7,23 @@ namespace odhtdb class LocalUser : public User { public: - static LocalUser* create(const std::string &name) + static LocalUser* create(const Signature::KeyPair &keyPair, const std::string &name) { - return new LocalUser(name); + return new LocalUser(keyPair, name); } + + const Signature::PublicKey& getPublicKey() const override + { + return keyPair.getPublicKey(); + } + + const Signature::PrivateKey& getPrivateKey() const + { + return keyPair.getPrivateKey(); + } + private: + LocalUser(const Signature::KeyPair &_keyPair, const std::string &name) : User(name), keyPair(_keyPair) {} private: - LocalUser(const std::string &name) : User(name){} + Signature::KeyPair keyPair; }; -} \ No newline at end of file +} diff --git a/include/RemoteUser.hpp b/include/RemoteUser.hpp new file mode 100644 index 0000000..770be61 --- /dev/null +++ b/include/RemoteUser.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "User.hpp" + +namespace odhtdb +{ + class RemoteUser : public User + { + public: + static RemoteUser* create(const Signature::PublicKey &publicKey, const std::string &name) + { + return new RemoteUser(publicKey, name); + } + + const Signature::PublicKey& getPublicKey() const override + { + return publicKey; + } + private: + RemoteUser(const Signature::PublicKey &_publicKey, const std::string &name) : User(name), publicKey(_publicKey){} + private: + Signature::PublicKey publicKey; + }; +} diff --git a/include/Signature.hpp b/include/Signature.hpp new file mode 100644 index 0000000..90d5278 --- /dev/null +++ b/include/Signature.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include + +namespace odhtdb +{ + const int PUBLIC_KEY_NUM_BYTES = 32; + const int PRIVATE_KEY_NUM_BYTES = 64; + + class InvalidSignatureKeySize : public std::runtime_error + { + public: + InvalidSignatureKeySize(const std::string &errMsg) : std::runtime_error(errMsg) {} + }; + + class SignatureGenerationException : public std::runtime_error + { + public: + SignatureGenerationException(const std::string &errMsg) : std::runtime_error(errMsg) {} + }; + + class DataSignException : public std::runtime_error + { + public: + DataSignException(const std::string &errMsg) : std::runtime_error(errMsg) {} + }; + + namespace Signature + { + class PublicKey + { + friend class KeyPair; + public: + // Throws InvalidSignatureKeySize if size is not PUBLIC_KEY_NUM_BYTES + PublicKey(char *data, size_t size); + PublicKey(const PublicKey &other); + PublicKey& operator=(const PublicKey &other); + + const char* getData() const { return data; } + size_t getSize() const { return PUBLIC_KEY_NUM_BYTES; } + + std::string toString() const; + private: + PublicKey(){} + private: + char data[PUBLIC_KEY_NUM_BYTES]; + }; + + class PrivateKey + { + friend class KeyPair; + public: + // Throws InvalidSignatureKeySize if size is not PRIVATE_KEY_NUM_BYTES + PrivateKey(char *data, size_t size); + PrivateKey(const PrivateKey &other); + PrivateKey& operator=(const PrivateKey &other); + + const char* getData() const { return data; } + size_t getSize() const { return PRIVATE_KEY_NUM_BYTES; } + + // Throws DataSignException if signing data failed for whatever reason. This wont happen unless there is an issue with the private key + std::string sign(const std::string &dataToSign) const; + std::string toString() const; + private: + PrivateKey(){} + private: + char data[PRIVATE_KEY_NUM_BYTES]; + }; + + class KeyPair + { + public: + // Throws SignatureGenerationException if generation of private/public key pair fails (should never happen) + KeyPair(); + + const PublicKey& getPublicKey() const { return publicKey; } + const PrivateKey& getPrivateKey() const { return privateKey; } + private: + PublicKey publicKey; + PrivateKey privateKey; + }; + } +} diff --git a/include/User.hpp b/include/User.hpp index e542434..ab5872a 100644 --- a/include/User.hpp +++ b/include/User.hpp @@ -1,5 +1,6 @@ #pragma once +#include "Signature.hpp" #include #include @@ -18,7 +19,10 @@ namespace odhtdb class User { public: + virtual ~User(){} + const std::string& getName() const { return name; } + virtual const Signature::PublicKey& getPublicKey() const = 0; protected: User(const std::string &_name) : name(_name) { @@ -26,7 +30,6 @@ namespace odhtdb throw UserNameTooLongException(name); } private: - // TODO: Add public key std::string name; }; -} \ No newline at end of file +} diff --git a/odhtdb.kdev4 b/odhtdb.kdev4 new file mode 100644 index 0000000..a0fab71 --- /dev/null +++ b/odhtdb.kdev4 @@ -0,0 +1,4 @@ +[Project] +CreatedFrom= +Manager=KDevCustomBuildSystem +Name=odhtdb diff --git a/project.conf b/project.conf index 8348027..2b9e556 100644 --- a/project.conf +++ b/project.conf @@ -10,4 +10,5 @@ opendht = "1.5.0" fmt = "4.1.0" libsodium = "1.0.16" ntpclient = "0.1.0" -sibs-serializer = "0.1.0" \ No newline at end of file +sibs-serializer = "0.1.0" +boost-filesystem = "1.66.0" \ No newline at end of file diff --git a/src/Database.cpp b/src/Database.cpp index c2d08cb..f0c7b04 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -1,6 +1,7 @@ #include "../include/Database.hpp" #include "../include/Group.hpp" -#include "../include/User.hpp" +#include "../include/LocalUser.hpp" +#include "../include/RemoteUser.hpp" #include #include #include @@ -22,11 +23,11 @@ static bool timestampSynced = false; static InfoHash CREATE_DATA_HASH = InfoHash::get("__odhtdb__.create_data"); static InfoHash ADD_DATA_HASH = InfoHash::get("__odhtdb__.add_data"); -#define OPENDHT_INFOHASH_LEN 20 +const int OPENDHT_INFOHASH_LEN = 20; namespace odhtdb { - Database::Database(const char *bootstrapNodeAddr, u16 port) + Database::Database(const char *bootstrapNodeAddr, u16 port, boost::filesystem::path storageDir) { node.run(port, dht::crypto::generateIdentity(), true); fmt::MemoryWriter portStr; @@ -126,21 +127,32 @@ namespace odhtdb serializer.add(stagedObject.timestamp); serializer.add((u8)stagedObject.primaryAdminGroup->getName().size()); serializer.add((u8*)stagedObject.primaryAdminGroup->getName().data(), stagedObject.primaryAdminGroup->getName().size()); - serializer.add((u32)stagedObject.primaryAdminGroup->getUsers().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 value(serializer.getBuffer().data(), serializer.getBuffer().size()); - node.put(CREATE_DATA_HASH, move(value), [](bool ok) + Value createDataValue(serializer.getBuffer().data(), serializer.getBuffer().size()); + node.put(CREATE_DATA_HASH, move(createDataValue), [](bool ok) { // TODO: Handle failure to put data if(!ok) fprintf(stderr, "Failed to put: %s, what to do?\n", "commitStagedCreateObject"); }, 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"); + }, time_point(), false); } void Database::commitStagedAddObject(const StagedAddObject &stagedObject) @@ -154,12 +166,21 @@ namespace odhtdb serializer.add((u8*)stagedObject.data.data, stagedObject.data.size); // TODO: Verify if serializer buffer needs to survive longer than this scope - Value value(serializer.getBuffer().data(), serializer.getBuffer().size()); - node.put(ADD_DATA_HASH, move(value), [](bool ok) + Value addDataValue(serializer.getBuffer().data(), serializer.getBuffer().size()); + node.put(ADD_DATA_HASH, move(addDataValue), [](bool ok) + { + // TODO: Handle failure to put data + if(!ok) + fprintf(stderr, "Failed to put for all: %s, what to do?\n", "commitStagedAddObject"); + }, 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: %s, what to do?\n", "commitStagedAddObject"); + fprintf(stderr, "Failed to put for listeners: %s, what to do?\n", "commitStagedAddObject"); }, time_point(), false); } @@ -182,6 +203,29 @@ namespace odhtdb 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) + { + 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); + } + + // NOTE: There might be more data in deserializer, but we can ignore those; we already got all data we need return result; } @@ -204,7 +248,9 @@ namespace odhtdb { try { + // TODO: Verify createObject timestamp is not in the future StagedCreateObject createObject = deserializeCreateRequest(value); + delete createObject.primaryAdminGroup; } catch (sibs::DeserializeException &e) { @@ -229,4 +275,4 @@ namespace odhtdb } return true; } -} \ No newline at end of file +} diff --git a/src/Signature.cpp b/src/Signature.cpp new file mode 100644 index 0000000..804047e --- /dev/null +++ b/src/Signature.cpp @@ -0,0 +1,97 @@ +#include "../include/Signature.hpp" +#include +#include +#include + +using namespace std; + +namespace odhtdb +{ + namespace Signature + { + PublicKey::PublicKey(char *_data, size_t size) + { + if(size != PUBLIC_KEY_NUM_BYTES) + { + string errMsg = "Expected public key size to be "; + errMsg += to_string(PUBLIC_KEY_NUM_BYTES); + errMsg += " bytes, was: "; + errMsg += to_string(size); + throw InvalidSignatureKeySize(errMsg); + } + memmove(data, _data, PUBLIC_KEY_NUM_BYTES); + } + + PublicKey::PublicKey(const PublicKey &other) + { + memmove(data, other.data, PUBLIC_KEY_NUM_BYTES); + } + + PublicKey& PublicKey::operator=(const PublicKey &other) + { + memmove(data, other.data, PUBLIC_KEY_NUM_BYTES); + return *this; + } + + string PublicKey::toString() const + { + string result; + result.resize(PUBLIC_KEY_NUM_BYTES * 2); + sodium_bin2hex(&result[0], PUBLIC_KEY_NUM_BYTES * 2 + 1, (const unsigned char*)data, PUBLIC_KEY_NUM_BYTES); + return result; + } + + PrivateKey::PrivateKey(char *_data, size_t size) + { + if(size != PRIVATE_KEY_NUM_BYTES) + { + string errMsg = "Expected private key size to be "; + errMsg += to_string(PRIVATE_KEY_NUM_BYTES); + errMsg += " bytes, was: "; + errMsg += to_string(size); + throw InvalidSignatureKeySize(errMsg); + } + memmove(data, _data, PRIVATE_KEY_NUM_BYTES); + } + + PrivateKey::PrivateKey(const PrivateKey &other) + { + memmove(data, other.data, PRIVATE_KEY_NUM_BYTES); + } + + PrivateKey& PrivateKey::operator=(const PrivateKey &other) + { + memmove(data, other.data, PRIVATE_KEY_NUM_BYTES); + return *this; + } + + string PrivateKey::sign(const string &dataToSign) const + { + string result; + 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) + throw DataSignException("Failed to sign data. Is private key invalid?"); + + if(resultSize != result.size()) + throw DataSignException("Failed to sign data. The signed data is not of expected size (bug)"); + + return result; + } + + string PrivateKey::toString() const + { + string result; + result.resize(PRIVATE_KEY_NUM_BYTES * 2); + sodium_bin2hex(&result[0], PRIVATE_KEY_NUM_BYTES * 2 + 1, (const unsigned char*)data, PRIVATE_KEY_NUM_BYTES); + return result; + } + + KeyPair::KeyPair() + { + 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/main.cpp b/tests/main.cpp index 5ddcbe9..a685ee6 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -7,9 +7,31 @@ using namespace odhtdb; int main() { + LocalUser *localUser = LocalUser::create(Signature::KeyPair(), "dec05eba"); + + std::string publicKeyStr = localUser->getPublicKey().toString(); + printf("Local user public key: %s\n", publicKeyStr.c_str()); + + std::string privateKeyStr = localUser->getPrivateKey().toString(); + printf("Local user private key: %s\n", privateKeyStr.c_str()); +/* + char hex_ed_pk[65]; + unsigned char seed[crypto_sign_ed25519_SEEDBYTES]; + unsigned char ed25519_skpk[crypto_sign_ed25519_SECRETKEYBYTES]; + unsigned char ed25519_pk[crypto_sign_ed25519_PUBLICKEYBYTES]; + + crypto_sign_ed25519_sk_to_seed(seed, ed25519_skpk); + crypto_sign_ed25519_seed_keypair(ed25519_pk, ed25519_skpk, seed); + sodium_bin2hex(hex_ed_pk, 65, ed25519_pk, 32); + printf("public key: %s\n", hex_ed_pk); + */ + + + //crypto_sign_ed25519_sk_to_seed // TODO: For tests, dont run against bootstrap.ring.cx. // Run against a bootstrap node made only for testing which doesn't persist added data. - Database database("bootstrap.ring.cx", 4222); + /* + Database database("bootstrap.ring.cx", 4222, "storage"); database.seed(); LocalUser *localUser = LocalUser::create("dec05eba"); @@ -20,5 +42,6 @@ int main() const char *data = "hello, world!"; database.add("galax.channel.latenight.chat", DataView{ (void*)data, strlen(data) }); database.commit(); + */ return 0; -} \ No newline at end of file +} -- cgit v1.2.3