From 67957afb6ba01bcd85f1abd1a50ad2c1aa813c7c Mon Sep 17 00:00:00 2001 From: Aleksi Lindeman <0xdec05eba@gmail.com> Date: Wed, 14 Feb 2018 22:18:48 +0100 Subject: Sign messages/verify message signatures --- .vscode/launch.json | 27 ++++++++++++++++++++++ include/Database.hpp | 5 ++-- include/Signature.hpp | 28 +++++++++++++++++++++- include/StagedObject.hpp | 6 ++--- src/Database.cpp | 39 ++++++++++++++++++++----------- src/Signature.cpp | 19 ++++++++++++--- tests/main.cpp | 60 ++++++++++++++++++++++++++++++++++++++++++------ 7 files changed, 153 insertions(+), 31 deletions(-) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..956830f --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "(gdb) Launch", + "type": "cppdbg", + "request": "launch", + "program": "tests/sibs-build/debug/test", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": true, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + } + ] +} \ No newline at end of file diff --git a/include/Database.hpp b/include/Database.hpp index 0104a6e..e8b35bb 100644 --- a/include/Database.hpp +++ b/include/Database.hpp @@ -12,7 +12,6 @@ namespace odhtdb { - class Group; class LocalUser; class Database @@ -22,8 +21,8 @@ namespace odhtdb ~Database(); void seed(); - void create(const Key &key, Group *primaryAdminGroup); - void add(const Key &key, DataView data, LocalUser *creator); + void create(LocalUser *owner, const Key &key); + void add(LocalUser *owner, const Key &key, DataView data); void commit(); private: void commitStagedCreateObject(const StagedCreateObject &stagedObject); diff --git a/include/Signature.hpp b/include/Signature.hpp index ea776ea..aace383 100644 --- a/include/Signature.hpp +++ b/include/Signature.hpp @@ -1,11 +1,13 @@ #pragma once +#include "DataView.hpp" #include namespace odhtdb { const int PUBLIC_KEY_NUM_BYTES = 32; const int PRIVATE_KEY_NUM_BYTES = 64; + const int SIGNED_HASH_SIZE = 64; class InvalidSignatureKeySize : public std::runtime_error { @@ -25,6 +27,25 @@ namespace odhtdb DataSignException(const std::string &errMsg) : std::runtime_error(errMsg) {} }; + class UnsignException : public std::runtime_error + { + public: + UnsignException(const std::string &errMsg) : std::runtime_error(errMsg) {} + virtual ~UnsignException(){} + }; + + class UnsignInvalidSizeException : public UnsignException + { + public: + UnsignInvalidSizeException(const std::string &errMsg) : UnsignException(errMsg) {} + }; + + class UnsignWrongKeyException : public UnsignException + { + public: + UnsignWrongKeyException(const std::string &errMsg) : UnsignException(errMsg) {} + }; + namespace Signature { class PublicKey @@ -41,6 +62,11 @@ namespace odhtdb const char* getData() const { return data; } size_t getSize() const { return PUBLIC_KEY_NUM_BYTES; } + // Throws UnsignWrongKeyException if signed message was not signed using the matching private key of this public key. + // Throws UnsignInvalidSizeException if signed message is too small (< SIGNED_HASH_SIZE). + // Both exceptions are derived from UnsignException + std::string unsign(const DataView &signedMessage) const; + std::string toString() const; private: PublicKey(){} @@ -61,7 +87,7 @@ namespace odhtdb 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 sign(const DataView &dataToSign) const; std::string toString() const; private: PrivateKey(){} diff --git a/include/StagedObject.hpp b/include/StagedObject.hpp index dc2aaf4..fccf4f6 100644 --- a/include/StagedObject.hpp +++ b/include/StagedObject.hpp @@ -26,13 +26,13 @@ namespace odhtdb struct StagedAddObject { Key key; - DataView data; + std::unique_ptr data; u64 timestamp; // In microseconds Signature::PublicKey creatorPublicKey; StagedAddObject() : key(), data(), timestamp(0), creatorPublicKey(Signature::PublicKey::ZERO) {} - StagedAddObject(const Key &_key, const DataView &_data, u64 _timestamp, const Signature::PublicKey &_creatorPublicKey) : - key(_key), data(_data), timestamp(_timestamp), creatorPublicKey(_creatorPublicKey) + StagedAddObject(const Key &_key, std::unique_ptr &&_data, u64 _timestamp, const Signature::PublicKey &_creatorPublicKey) : + key(_key), data(std::move(_data)), timestamp(_timestamp), creatorPublicKey(_creatorPublicKey) { } diff --git a/src/Database.cpp b/src/Database.cpp index e3b9f3d..90e83c1 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -85,18 +85,21 @@ namespace odhtdb node.listen(ADD_DATA_HASH, bind(&Database::listenAddData, this, _1)); } - void Database::create(const Key &key, Group *primaryAdminGroup) + void Database::create(LocalUser *owner, const Key &key) { + Group *primaryAdminGroup = new Group("admin"); + primaryAdminGroup->addUser(owner); // TODO: Append fractions to get real microseconds time u64 timeMicroseconds = ((u64)getSyncedTimestampUtc().seconds) * 1000000ull; stagedCreateObjects.emplace_back(StagedCreateObject(key, primaryAdminGroup, timeMicroseconds)); } - void Database::add(const Key &key, DataView data, LocalUser *creator) + void Database::add(LocalUser *owner, const Key &key, DataView data) { + 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, data, timeMicroseconds, creator->getPublicKey())); + stagedAddObjects.emplace_back(StagedAddObject(key, move(signedData), timeMicroseconds, owner->getPublicKey())); } void Database::commit() @@ -108,6 +111,7 @@ namespace odhtdb for(StagedCreateObject &stagedObject : stagedCreateObjects) { commitStagedCreateObject(stagedObject); + delete stagedObject.primaryAdminGroup; } stagedCreateObjects.clear(); @@ -177,9 +181,9 @@ namespace odhtdb serializer.add(stagedObject.key.hashedKey.data(), OPENDHT_INFOHASH_LEN); serializer.add(stagedObject.timestamp); serializer.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); + assert(stagedObject.data->size() < 0xFFFF - 120); + serializer.add((u16)stagedObject.data->size()); + serializer.add((u8*)stagedObject.data->data(), stagedObject.data->size()); // TODO: Verify if serializer buffer needs to survive longer than this scope Value addDataValue(serializer.getBuffer().data(), serializer.getBuffer().size()); @@ -262,11 +266,15 @@ namespace odhtdb Signature::PublicKey creatorPublicKey(creatorPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); u16 dataSize = deserializer.extract(); - char *data = (char*)malloc(dataSize); - if(!data) - throw sibs::DeserializeException("Failed to allocate memory for add request"); - result.data.data = data; - result.data.size = dataSize; + if(dataSize < SIGNED_HASH_SIZE) + throw sibs::DeserializeException("Signed data is too small"); + + string signedData; + signedData.resize(dataSize); + deserializer.extract((u8*)&signedData[0], dataSize); + result.data = make_unique(); + result.data->resize(dataSize); + result.data = make_unique(creatorPublicKey.unsign(DataView((void*)signedData.data(), signedData.size()))); return result; } @@ -279,7 +287,6 @@ namespace odhtdb // TODO: Verify createObject timestamp is not in the future StagedCreateObject createObject = deserializeCreateRequest(value); databaseStorage.createStorage(createObject.key, { createObject.primaryAdminGroup }, createObject.timestamp); - //delete createObject.primaryAdminGroup; } catch (sibs::DeserializeException &e) { @@ -299,8 +306,8 @@ namespace odhtdb { // TODO: Verify createObject timestamp is not in the future StagedAddObject addObject = deserializeAddRequest(value); - databaseStorage.appendStorage(addObject.key, addObject.data, addObject.timestamp, addObject.creatorPublicKey); - //free(addObject.data.data); + DataView data((void*)addObject.data->data(), addObject.data->size()); + databaseStorage.appendStorage(addObject.key, data, addObject.timestamp, addObject.creatorPublicKey); } catch (sibs::DeserializeException &e) { @@ -310,6 +317,10 @@ namespace odhtdb { fprintf(stderr, "Warning: Failed to deserialize 'add' request: %s\n", e.what()); } + catch (UnsignException &e) + { + fprintf(stderr, "Warning: Failed to deserialize 'add' request: %s\n", e.what()); + } return true; } } diff --git a/src/Signature.cpp b/src/Signature.cpp index 946d754..8d3654d 100644 --- a/src/Signature.cpp +++ b/src/Signature.cpp @@ -35,6 +35,19 @@ namespace odhtdb return *this; } + string PublicKey::unsign(const DataView &signedMessage) const + { + if(signedMessage.size < SIGNED_HASH_SIZE) + throw UnsignInvalidSizeException("Signed message is too small (corrupt or malicious signed message)"); + + 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) + throw UnsignWrongKeyException("Message was not signed with matching private key"); + + return result; + } + string PublicKey::toString() const { string result; @@ -67,13 +80,13 @@ namespace odhtdb return *this; } - string PrivateKey::sign(const string &dataToSign) const + string PrivateKey::sign(const DataView &dataToSign) const { string result; - result.resize(crypto_sign_ed25519_BYTES + dataToSign.size()); + 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()) diff --git a/tests/main.cpp b/tests/main.cpp index 8818ff9..1940c1a 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -9,15 +9,63 @@ using namespace std; using namespace chrono_literals; using namespace odhtdb; -int main() +#define assertEquals(a, b) do { if((a) != (b)) { fprintf(stderr, "Assert failed:\n%s != %s\n", #a, #b); exit(1); } } while(0) +void fail(const string &errMsg) { fprintf(stderr, "Fail:\n%.*s\n", errMsg.size(), errMsg.c_str()); } + +void testSignData(LocalUser *localUser) { - 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()); + + string expectedUnsignedData = "hello, world!"; + string signedData = localUser->getPrivateKey().sign(DataView((void*)expectedUnsignedData.data(), expectedUnsignedData.size())); + assertEquals(SIGNED_HASH_SIZE + expectedUnsignedData.size(), signedData.size()); + + string unsignedData = localUser->getPublicKey().unsign(DataView((void*)signedData.data(), signedData.size())); + assertEquals(expectedUnsignedData, unsignedData); + + try + { + Signature::KeyPair anotherKeyPair; + anotherKeyPair.getPublicKey().unsign(DataView((void*)signedData.data(), signedData.size())); + fail("Expected unsign to fail since the data was not signed with the private key matching given public key"); + } + catch (UnsignWrongKeyException &e) + { + + } + catch (exception &e) + { + string errMsg = "Expected unsign to fail with UnsignWrongKeyException, fail reason: "; + errMsg += e.what(); + fail(errMsg); + } + + try + { + Signature::KeyPair anotherKeyPair; + anotherKeyPair.getPublicKey().unsign(DataView((void*)signedData.data(), 3)); + fail("Expected unsign to fail since the (signed) data is too small to have been signed (signed hash is 64 bytes)"); + } + catch (UnsignInvalidSizeException &e) + { + + } + catch (exception &e) + { + string errMsg = "Expected unsign to fail with UnsignInvalidSizeException, fail reason: "; + errMsg += e.what(); + fail(errMsg); + } +} + +int main() +{ + LocalUser *localUser = LocalUser::create(Signature::KeyPair(), "dec05eba"); + testSignData(localUser); // TODO: For tests, dont run against bootstrap.ring.cx. // Run against a bootstrap node made only for testing which doesn't persist added data. @@ -25,12 +73,10 @@ int main() Database database("bootstrap.ring.cx", 4222, "storage"); database.seed(); - Group group("admin"); - group.addUser(localUser); - database.create("galax.channel.latenight.chat", &group); + database.create(localUser, "galax.channel.latenight.chat"); const char *data = "hello, world!"; - database.add("galax.channel.latenight.chat", DataView{ (void*)data, strlen(data) }, localUser); + 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) -- cgit v1.2.3